# DKE Library — Reference **Version:** 1.0.2, changed in rev 150 · alpha · API 1.2 · rev 150\ **Status:** Reference for the DKE Library.\ **Audience:** Customers using a published reasoning module in their own store, and the programs that write its facts.\ **This document as Markdown:** [dke-library-ref.md](/ref/dke/library/dke-library-ref.md) — the same text this page is rendered from, for readers and tools that would rather have the source than the page. This is the lookup for the DKE Library. §1 to §25 cover the reasoning modules, taking the published ones one by one: the kinds each declares, the fields you write, the fields it derives for you, and the queries you can call. §28 lists the public names of `dkepy`, the package that writes your facts, and §29 places `dkekey`. The [Library Tutorial](/tutorial/dke/library/) teaches how to use them; this page is what you consult once you are using them. Module semantics in general — how `import` resolves, how namespacing works, what re-submitting does — are in the [Python Reference §9](/ref/dke/python/#modules). This page covers what the individual modules contain. --- ## 1. What a module declares Up to five kinds of declaration, all of which become part of your store the moment you import it: | Declaration | What it is | |---|---| | **Classes** | the kinds of thing the domain has, and the fields they carry | | **Derived fields** | values the engine works out from the ones you wrote, and keeps current as those change — you never write one | | **Standing rules** | conclusions that hold exactly while their condition holds over your data, and withdraw themselves when it stops | | **Standing constraints** | conditions your data is expected to meet — `checkup` names every subject that breaks one, and no write is refused for breaking one. Of the published modules `agenda` ([§5](#agenda-what-you-have-taken-on-and-what-you-may-not-do)), `quantum` ([§6](#quantum-circuits-devices-and-what-was-computed-about-them)) and `pqc` ([§7](#pqc-what-to-move-off-vulnerable-cryptography-first)) declare some | | **Queries** | `def`s you call by name, which answer over whatever facts you have loaded | Names are scoped to the module: `code.Function` and `finance.Function` are different kinds and never read each other's data. Derived fields and rule conclusions carry a **`derived` provenance** rather than a source, so you can always tell a conclusion from something you were told. --- ## 2. `code` — reasoning about a codebase `code` reasons about a codebase: import it, load facts about your own source, and ask. **Version 1.12.3.** Download it at [dke.langsyn.com/source/code/latest/code.dpy](https://dke.langsyn.com/source/code/latest/code.dpy), which always serves the current release, or name the version itself at `https://dke.langsyn.com/source/code/1.12.3/code.dpy`. A versioned URL says exactly which bytes you read and serves them until the next release, which replaces the previous version rather than keeping it — so `latest` is the link that goes on lasting, and the copy you have already downloaded is yours either way. A version never changes without its content changing, so the version you hold answers whether you are current without your having to diff anything. ### 2.1 Kinds and the fields you write | Kind | Fields you write | |------|------------------| | `code.Function` | `name`, `module`, `lines`, `branches`, `loops`, `deprecated`, `test`, `called`, `returns_ignored`, `language`, `callers`, `callees`, `explained` | | `code.Class` | `name`, `module`, `methods`, `language` | | `code.Call` | `caller`, `callee`, `site` | | `code.UnresolvedCall` | `caller`, `site`, `text` | | `code.Rationale` | `subject`, `marker`, `text`, `site` | | `code.Import` | `importer`, `imported`, `root` | | `code.Dependency` | `name`, `version`, `direct` (absent when transitive), `pinned` | | `code.Counterpart` | `name`, `left`, `right`, `relation`, `left_covers`, `right_covers` | **Every query that answers "which functions" returns subjects**, never display names — the same identity `code.Call.caller` holds and `callers_of(fn)` takes. So an answer from one query feeds straight into another, and `name` stays what it is: an optional label for reading, not an identity. Three fields decide whether the queries work at all: - **`code.Call.caller` and `code.Call.callee` hold the subject you recorded a `Function` under**, not its display name. The queries compare them directly, so display names make every function look uncalled, with nothing to tell you why. - **`code.Function.test`** names a test that reaches the function, and `untested` reads its **absence**. Write it only where a test really does reach, or the rule falls silent everywhere instead of where it should be. - **`code.Function.called`** says something in this codebase calls this function, and `uncalled` reads its **absence** — the same shape as `test`. Record it on the CALLEE, next to the `Call` you record for the same edge. That looks redundant and is not: `uncalled` reads a field of the function, not the `Call` kind, so the absence is a fact about this function only when it is recorded here. **Recording the `Call` alone is not enough** — write both, or every function you recorded comes back `uncalled` however many callers the store holds. **`code.Function.callers` and `code.callees`** are degree — how many **distinct** functions call this one, and how many it calls. Distinct neighbours, not call sites: a caller that calls you three times is one dependency, not three, and counting sites would rank a function by how chatty its callers happen to be. They are recorded rather than counted for you, for the same reason `called` is — and unlike `called`, write them for **every** function, zero included. Those above are read by their absence, so a value everywhere silences the rule. `callers` is read by comparison, and a function nothing calls has a fan-in of zero, which is a fact rather than a gap. Optional and absent-tolerant: record neither and everything else answers the same, with `hub` simply never concluding. **`code.UnresolvedCall`** is the opposite record: a call you saw and could *not* name a target for. It asserts no edge, deliberately — a missing edge is a known gap, and a guessed one is a wrong answer no query could tell from a fact. Write `caller` as the subject of the enclosing function (as on `Call`), `site` as the location to look at, and `text` as the callee exactly as it appears in the source. `site` is what makes an entry worth having; a subject without one is skipped. This kind is optional and absent-tolerant — see §2.5 for what it buys you. **`code.Rationale`** is a reason the author wrote down, attached to what it explains. Every other kind here is structure — something a reader could recompute from your source. This is the part that cannot be recomputed: *why* the structure is the way it is. It is **stated, never inferred**, for the reason `Counterpart` is: intent is a claim only a reader can make, and an inferred one produces facts you cannot tell from the real ones. Write `subject` as the subject of the `Function` or `Class` it explains, `marker` as which convention it was, `text` as what the author wrote, and `site` as where to read it in full. `text` is what makes an entry worth having; a subject without one is skipped. **Which comments count is your producer's decision**, exactly as what counts as a `branch` is — and the same warning applies in the same direction. A convention wide enough to sweep ordinary comments turns every line in your tree into a fact and `rationale_for` into noise. Choose something an author had to type on purpose. Ours records `WHY:` and `NOTE:` at the start of a comment, uppercase, and nothing else. **`code.Function.explained`** says a reason exists, and `unexplained_critical` reads its **absence** — the same shape as `test` and `called`. Write it only where a reason really was recorded, or the rule falls silent everywhere instead of firing. **`language`** says what a fact is *about* — `python`, `cpp`, `rust`, whatever you call it. It is optional, and a store that never records it answers every other question the same. Record it once your store holds more than one language: `module` is a path, so it hints at the language at best and no query can rely on it, and without `language` there is no way to ask about one half of a repository. It also tells two producers apart, which matters because `branches` is whatever *your* analysis counted, and two producers can both be right on different scales while `complexity` compares them against one threshold either way. **`code.Counterpart`** records two functions that have to agree — an encoder and its decoder, a writer and its reader, two siblings that ought to guard their inputs alike. Nothing in a call graph says two functions are halves of one contract; that is a claim about intent, and only you know it. State the pair, name in `relation` what is being counted, and give the count each side handles. `left_covers` and `right_covers` count members of that one named set, which makes them comparable to each other and to nothing else. ### 2.2 Derived and maintained for you | Field | Meaning | |-------|---------| | `code.Function.complexity` | one path, plus one per branch and per loop — see §2.3 | | `code.Function.hotspot` | complexity above 10 | | `code.Function.oversized` | more than 200 lines | | `code.Function.untested` | has lines recorded, and no test was seen to reach it | | `code.Function.uncalled` | has lines recorded, and nothing was seen to call it | | `code.Function.hub` | more than 5 distinct functions call it | | `code.Function.critical_untested` | complexity above 10, and no test was seen to reach it | | `code.Function.unexplained_critical` | the same, and no reason was recorded either | | `code.Function.discards_result` | throws away at least one result it was handed | | `code.Class.sprawling` | more than 20 methods | | `code.Counterpart.gap` / `reverse_gap` | how far apart the two sides are, each direction as a positive number | | `code.Counterpart.left_stricter` | the left side handles more — the right accepts what the left refuses | | `code.Counterpart.right_stricter` | the same, the other way round | ### 2.3 How to count `branches` and `loops` `hotspot` fires at a complexity above 10. A fixed threshold over a number you compute yourself only means something if we agree on how it is computed, so here is the rule. Follow it and your numbers are comparable — to your own past numbers, to another producer's, and to the threshold. `complexity` is **one, plus one for each decision the function makes**: | Count one for | Notes | |---------------|-------| | each `if` (and each `else if` / `elif`) | `else` counts nothing | | each loop | `for`, `while`, `do`, and each clause of a comprehension | | each `case` arm that tests | a catch-all `default` / `case _` counts nothing | | each exception handler | one per `except` / `catch` clause | | each short-circuit boolean | `and` / `or`, `&&` / `\|\|` | | each conditional expression | the `a ? b : c` / `b if a else c` form | `else`, `default` and a bare `case _` are where control lands when none of the decisions above them fired, so they add nothing — three arms and a catch-all is three, not four. Two scope rules decide what belongs to which function: - a **named** nested function is its own `Function`, with its own counts; its decisions are not charged to the function that contains it; - an **anonymous** inline function — a lambda, a closure, a block — counts toward the function containing it. It is part of that function's text and cannot be called from anywhere else, so there is no other function for its decisions to belong to. **The list is closed.** Anything not on it counts nothing, including constructs with a fair claim to being a decision. An `assert` is the clearest one: it is a conditional failure, and most complexity tools count it — but in some languages it is a macro or a compiled-out call that a reader working from source cannot reliably see, and a rule that some producers can follow and others cannot is the problem this rule exists to solve. A number everyone computes the same way is worth more than a number that is arguably more faithful. If you want a different scale — a stricter threshold, a metric of your own — define your own module named `code`. ### 2.4 Queries | Call | Answers | |------|---------| | `code.callers_of(fn)` | everything that calls `fn`, directly | | `code.callees_of(fn)` | everything `fn` calls, directly | | `code.impact_of(fn)` | what breaks if `fn` changes — callers, and theirs, to three hops | | `code.importers_of(module)` | every module importing `module` | | `code.uncalled()` | functions with no recorded caller | | `code.unused_dependencies()` | dependencies you declared that nothing imports | | `code.unpinned_dependencies()` | dependencies declared as a range, not an exact version | | `code.asymmetries()` | pairs whose two sides do not handle the same amount, and which way | | `code.discarded_results()` | functions that throw a result away, so a failure cannot reach the caller | | `code.critical()` | complicated and unreached — where a defect is likeliest to survive | | `code.unexplained()` | `critical()` with everything already accounted for taken out | | `code.rationale_for(fn)` | why `fn` is the way it is, in the author's own words | | `code.functions_in(language)` | every function recorded as belonging to one language | | `code.hotspots_in(language)` | the complicated ones in one language | | `code.hubs()` | the most depended-on functions, most first — what breaks most if you touch it | | `code.dispatch_sites()` | every call whose target was not determined — where reading stopped | ### 2.5 What the answers can and cannot mean The queries are only as good as the data, and for a codebase that has consequences worth stating plainly. **A call graph derived from source is a lower bound.** Python decides calls as it runs, so anything reading source sees the calls the source makes plain and no others — a call through a variable, a handler table or `getattr` is not visible. Every edge you record that way is real; edges are missing. That matters most for `uncalled()`: a function reached only dynamically appears there. Read it as candidates to look at, never a list to delete. **How loose that bound is, is measurable.** Record an `UnresolvedCall` wherever your producer sees a call and cannot name the target, and `dispatch_sites()` lists them: the location, and the callee as written. Read it beside `uncalled()` — any of those sites could be the call that a function on that list is reached by. A codebase with two dispatch sites and one with two hundred deserve very different trust in the same `uncalled()` answer, and this is what tells them apart. Nothing on the list is a defect by itself: a plugin table, a callback and a decorator all work exactly this way. It is a measure of how much of your call graph is decidable by reading. Recording none of them is fine. The kind is absent-tolerant — everything else answers the same, and `dispatch_sites()` returns nothing. **`untested` inherits that bound**, since reaching a function is worked out over the same graph — and reaching is not executing, because a branch may never be taken. Read it the same way. **`critical()` cannot tell deliberate from overlooked, and `unexplained()` can.** Two functions can be identical in every structural respect — same complexity, neither reached by a test — and be entirely different objects: one whose author wrote down why it is shaped that way and why it is not tested, and one that says nothing. `critical()` returns both, because everything it reads is structure. Record `Rationale` and `explained` and `unexplained()` is the same list with the accounted-for taken out. A short `unexplained()` under a long `critical()` is a good result: the risk is known and written down rather than absent. Record none of it and `unexplained()` is simply `critical()` again, which is where you were. **There is no dependency age**, and that is the schema being honest rather than an omission. Python's package metadata carries no release date, so an age could only come from asking an index — which means sending a dependency list to a third party. A field nothing can honestly fill is worse than no field, because it reads as a capability. **`unused_dependencies()` matches by name**, so a distribution whose import name differs from its package name (Pillow is imported as `PIL`) will look unused. **A subject missing the field a query reads is skipped, not fatal.** You are never obliged to fill every field of a kind, and one half-written subject does not take out the answer for the complete ones beside it — a `Dependency` with no `name`, a `Counterpart` with no `relation`, an `UnresolvedCall` with no `site` and a `Rationale` with no `text` are each passed over, and everything else answers as it would have. So a partial record costs you that record and nothing more. --- ## 3. `identity` — which of your records name the same thing `identity` reasons about records that may describe one thing: two customer entries, a person in two systems, a supplier under two spellings. You record the records, and what your sources say about them — that two are the same, might be, or are not — and it works out which records name the same thing, keeps the possibilities apart from the facts, and reports where your sources disagree. **Version 1.0.2.** Download it at [dke.langsyn.com/source/identity/latest/identity.dpy](https://dke.langsyn.com/source/identity/latest/identity.dpy), which always serves the current release, or name the version itself at `https://dke.langsyn.com/source/identity/1.0.2/identity.dpy`. **Nothing is merged.** Every statement stays a claim with the source that made it. That two records are the same is a conclusion you read, and it withdraws itself when a statement it rests on is forgotten. The records themselves never change. ### 3.1 Kinds and the fields you write | Kind | Fields you write | |------|------------------| | `identity.Record` | `name` | | `identity.Same` | `a`, `b` | | `identity.Possibly` | `a`, `b` | | `identity.Different` | `a`, `b` | A **record** is a subject of `identity.Record`, and `name` is what it is called for reading. The rules do not need it: **every name a statement uses is a record to them, recorded or not.** So a misspelt name in a statement is not refused. It becomes a record of its own, and shows up under the misspelling in `same_as`. `identity.unrecorded()` lists every name a statement uses that no record carries. A **statement** is a subject of `Same`, `Possibly` or `Different`, and `a` and `b` hold the names of the two records it is about. The statement's own name is yours to choose, and only keeps two statements apart. Its source is the source you write it with, so who said two records are the same is recorded with the saying: ```python remember(identity.Record.alice.name, "Alice Smith", "crm") remember(identity.Record.asmith.name, "A. Smith", "billing") remember(identity.Same.m1.a, "alice", "crm") remember(identity.Same.m1.b, "asmith", "crm") ``` ### 3.2 Derived and maintained for you | Field | Meaning | |-------|---------| | `identity.Different.contradicted` | other statements, taken together, make its two records the same | | `identity.Record.contested` | a contradicted `Different` statement names this record | Two **relations** are concluded as well. A relation is read with both record names, `SameEntity.alice.asmith`, and holds `True` or nothing: | Relation | Holds when | |----------|------------| | `SameEntity.x.y` | a chain of `Same` statements, each read either way round, connects `x` to `y`. A record in any chain is also the same entity as itself. | | `PossiblySame.x.y` | a `Possibly` statement names `x` and `y`, either way round. Possibility is not chained: two possibilities in a row do not make a third. | A relation a module concludes is **not namespaced by the module**: it is `SameEntity`, never `identity.SameEntity`, in any unit, whether or not that unit imports `identity` ([Python Reference §9.6](/ref/dke/python/#namespacing)). That is what lets your own rules read it. Two consequences follow from the same rule. A path you write naming `SameEntity` or `PossiblySame`, with no `class` of that name in scope, writes into these relations. And a unit that declares a `class` named either one reads its own class by that name, so its rules cannot reach the relation. ### 3.3 Queries | Call | Answers | |------|---------| | `identity.same_as(r)` | every other record that names the same thing as `r` | | `identity.possibly_same_as(r)` | every record a source says might name the same thing as `r` | | `identity.contested()` | the records a contradicted `Different` statement names | | `identity.contradictions()` | the `Different` statements other statements contradict, by the names you gave them | | `identity.unrecorded()` | every name a statement uses that no record carries — usually a misspelling | ### 3.4 Carrying a fact across a link `identity` does not know what your records hold, so it cannot carry an email or an address from one record to another. A rule of yours can, by reading `SameEntity`. Name your own subjects after the records they describe, and conclude the carried value into a field of its own: ```python class Person: email: string @rule def email_seen(): for p in Person, q in Person: if SameEntity.p.q == True: p.email_seen = q.email ``` `p.email_seen` then holds the email of every record that names the same thing as `p`, `p`'s own included. Where two of them differ, the field holds both, and the difference is an ordinary disagreement: `conflicts()` reports it, `get(Person.p.email_seen)` answers each value, and `why` names the record each one came from. Carrying a field onto **itself** (`p.email = q.email`) is not the way to do this: a field a rule concludes cannot also be written, so the rule would have nothing to copy. A record that is in no chain is not the same entity as itself, so the rule above carries nothing to it. Where you want every record's own value included, say so with a rule of its own: ```python @rule def own_email(): for p in Person: if p.email != "": p.email_seen = p.email ``` ### 3.5 What the answers can and cannot mean **`SameEntity` is exactly as right as your `Same` statements.** One wrong statement joins two entities, and everything either one reaches. The conclusion names what it rests on — `why(SameEntity.x.y)` answers the statements, with the conclusions between them — and forgetting the wrong one withdraws every conclusion it supported, and nothing else. **A `Different` statement does not undo a `Same` one.** Where they disagree, both stand, and the disagreement is reported on the statement and on both records. Which source to believe is yours to decide; the module decides nothing. **A name is matched exactly.** `alice` and `Alice` are two records, and so are a record and its misspelling. Nothing here compares spellings; a statement is what says two names are one thing. `unrecorded()` is the check that catches a statement naming a record you never recorded. **The relation grows with the square of an entity.** An entity of *n* records holds *n*² `SameEntity` conclusions, one for each ordered pair. That is what makes every question about it one read, and it is the cost to keep in mind before stating that ten thousand records are one thing. --- ## 4. `argument` — which of your claims stand `argument` reasons about claims that bear on one another: a finding and the evidence for it, a proposal and the objections to it, a diagnosis and what rules it out. You record the claims, and what your sources say about them — that one claim supports another, attacks it, or weakens it — and it works out which claims stand, which fall, and which the statements leave open. **Version 1.0.2.** Download it at [dke.langsyn.com/source/argument/latest/argument.dpy](https://dke.langsyn.com/source/argument/latest/argument.dpy), which always serves the current release, or name the version itself at `https://dke.langsyn.com/source/argument/1.0.2/argument.dpy`. **Nothing is decided for you.** Every statement stays a claim with the source that made it. That a claim stands or falls is a conclusion you read, and it withdraws itself when a statement it rests on is forgotten. ### 4.1 Kinds and the fields you write | Kind | Fields you write | |------|------------------| | `argument.Claim` | `text` | | `argument.Support` | `by`, `target` | | `argument.Attack` | `by`, `target` | | `argument.Weakening` | `by`, `target` | A **claim** is a subject of `argument.Claim`, and `text` is what it says, for reading. As in `identity`, **every name a statement uses is a claim to the rules, recorded or not**, so a misspelt name becomes a claim of its own. `argument.unrecorded()` lists every name a statement uses that no claim carries. A **statement** is a subject of `Support`, `Attack` or `Weakening`. It says that the claim named in `by` supports, attacks or weakens the claim named in `target`, and its source is who says so. A statement counts once **both** of its fields are written; one with only a `target` bears on nothing. ```python remember(argument.Claim.leak.text, "The outage was caused by a memory leak", "ops") remember(argument.Claim.flat.text, "Memory stayed flat for the whole outage", "metrics") remember(argument.Attack.k1.by, "flat", "metrics") remember(argument.Attack.k1.target, "leak", "metrics") ``` ### 4.2 Derived and maintained for you | Field | Meaning | |-------|---------| | `argument.Claim.attacked` | an attack names this claim as its target | | `argument.Claim.unattacked` | no attack names this claim. It **stands**. | | `argument.Claim.undefended` | a claim nothing attacks attacks this one. It **falls**. | | `argument.Claim.reinstated` | this claim is attacked, and every claim attacking it falls. It **stands again**. | | `argument.Claim.weakened` | a weakening names this claim as its target | | `argument.Attack.answered` | the claim making this attack falls, so it no longer counts against its target | | `argument.Support.contested` | the claim giving this support is itself attacked | `argument` concludes no relation, so nothing it derives lives outside its own kinds. ### 4.3 Queries | Call | Answers | |------|---------| | `argument.supporters_of(c)` | every claim a source says supports `c` | | `argument.attackers_of(c)` | every claim a source says attacks `c` | | `argument.weakeners_of(c)` | every claim a source says weakens `c` | | `argument.unattacked()` | the claims nothing attacks, each of which stands | | `argument.undefended()` | the claims attacked by a claim nothing attacks, each of which falls | | `argument.reinstated()` | the attacked claims whose attackers have all fallen, each of which stands again | | `argument.weakened()` | the claims a weakening reaches | | `argument.contested_premises()` | the claims that support another and are themselves attacked | | `argument.unrecorded()` | every name a statement uses that no claim carries — usually a misspelling | ### 4.4 What the answers can and cannot mean **Three answers, and silence.** A claim stands when nothing attacks it, falls when a claim that stands attacks it, and stands again when every claim attacking it falls. An attacked claim that is none of these is **open**: no query names it. Two claims that attack each other are the simplest case, and a longer chain of attacks is the other. `argument` stops after these two steps on purpose, so that every claim it does name is settled by what you recorded. An open claim is one the statements do not settle this way, not one that has been found to fall. **A weakening is reported, never counted.** It does not make a claim fall and it does not answer an attack. Use it for evidence against a claim that you want on record without deciding anything. **A support is not a defence.** Supporting a claim does not protect it from an attack. What a support does is carry a doubt: `contested_premises()` names the claims that support something while being attacked themselves, which is where an argument rests on ground that is disputed. **It reads your statements, not your data.** A claim here is a name and a sentence. Where two sources give a field of one subject two different values, that disagreement is already the store's own finding (`conflicts()`), and you do not need this module to see it. `argument` is for claims that bear on each other because someone said they do. --- ## 5. `agenda` — what you have taken on, and what you may not do `agenda` reasons about what an agent or a person has to do: what you have undertaken and by when, what you owe someone, the goals your commitments serve, and the actions you may not take. You record them, and it tells you what is overdue at a moment you name, what is coming up, what was done late, which goals are met, and where a commitment or an obligation runs into a prohibition. **Version 1.0.1.** Download it at [dke.langsyn.com/source/agenda/latest/agenda.dpy](https://dke.langsyn.com/source/agenda/latest/agenda.dpy), which always serves the current release, or name the version itself at `https://dke.langsyn.com/source/agenda/1.0.1/agenda.dpy`. **Nothing acts, and nothing is chosen for you.** The store keeps no clock and sends no reminder. Every question that depends on the time takes the time as an argument: you ask what is overdue *at* a moment, and asking about that moment again gives the same answer. Where what you owe and what you may not do disagree, the module names the disagreement and leaves the choice to you. ### 5.1 Kinds and the fields you write | Kind | Fields you write | |------|------------------| | `agenda.Goal` | `text` | | `agenda.Commitment` | `action`, `due`, `serves`, `done_at` | | `agenda.Obligation` | `action`, `due`, `owed_to`, `done_at` | | `agenda.Prohibition` | `action` | A **commitment** is something you have undertaken to do. `action` says what, `due` is the `datetime` it is due, and `serves` names the goal it is for, if it serves one. An **obligation** is something you owe `owed_to`, and is read the same way; the difference is that you did not choose it. Either is **open** until `done_at` records when it was done. A **prohibition** is an action you may not take. Write it with a validity window to say when it applies (a change freeze, a quiet period) and it applies only then. Written without one, it always applies. A prohibition covers a commitment or an obligation whose `action` is exactly its own. As in `identity`, **every name a commitment serves is a goal to the rules, recorded or not**. `agenda.unrecorded()` lists every such name that no goal carries. ```python remember(agenda.Goal.launch.text, "Launch the new site", "me") remember(agenda.Commitment.docs.action, "write the docs", "me") remember(agenda.Commitment.docs.due, datetime("2026-10-02T17:00:00Z"), "me") remember(agenda.Commitment.docs.serves, "launch", "me") remember(agenda.Prohibition.freeze.action, "deploy", "ops", datetime("2026-10-05T00:00:00Z"), datetime("2026-10-12T00:00:00Z")) ``` **Moving a due date.** Move one with `update`, under the source that set it: ```python update(agenda.Commitment.docs.due, datetime("2026-10-06T17:00:00Z"), "me") ``` The date it replaces is kept, and `agenda.previous_dues("Commitment.docs")` reads it back. A revision replaces only what the same source said, so a date a *different* source gives sits beside the first instead of replacing it, and the item is then unsettled (§5.4). ### 5.2 Derived and maintained for you | Field | Meaning | |-------|---------| | `agenda.Commitment.forbidden` | a prohibition covers this commitment's action. It holds while the prohibition applies, whenever the commitment is due. | | `agenda.Obligation.forbidden` | a prohibition covers this obligation's action: what you owe and what you may not do disagree | | `agenda.Commitment.late` | this commitment was done after it was due | | `agenda.Obligation.late` | this obligation was done after it was due | | `agenda.Goal.pursued` | a commitment serves this goal | | `agenda.Goal.met` | every commitment serving this goal is done | **Two standing constraints** report through `checkup` rather than through a query. `agenda.nothing_forbidden_committed` names each open commitment a prohibition covers now, and `agenda.nothing_forbidden_owed` names each such obligation. Neither refuses a write: recording a commitment you may not keep is not an error, and the report is how you find it. ### 5.3 Queries | Call | Answers | |------|---------| | `agenda.overdue(at)` | every commitment and obligation due before `at` and not done by then | | `agenda.due_within(at, span)` | every commitment and obligation not done by `at` and due from `at` until `span` after it — what is coming up | | `agenda.unsettled(at)` | every commitment and obligation whose due date or completion two sources give differently at `at` | | `agenda.forbidden(at)` | every commitment and obligation open at `at` whose action a prohibition covers at `at` — what you may not do at that moment, whenever it is due | | `agenda.blocked()` | every open commitment and obligation whose action a prohibition covers at the moment it is due | | `agenda.late()` | every commitment and obligation done after it was due | | `agenda.met()` | every goal whose commitments are all done | | `agenda.serving(g)` | every commitment that serves goal `g` | | `agenda.previous_dues(item)` | every due date `item` had before the one it has now, oldest first | | `agenda.unrecorded()` | every goal a commitment serves that no goal carries — usually a misspelling | `at` is a `datetime` and `span` a `duration`. A query answers commitments and obligations as `Commitment.` and `Obligation.`, which is also how `previous_dues` takes one: ```python import agenda def this_week(): now = datetime("2026-10-01T09:00:00Z") for item in agenda.overdue(now): print("overdue: " + item) for item in agenda.due_within(now, duration("P7D")): print("this week: " + item) ``` ### 5.4 What the answers can and cannot mean **The moment is always yours to name.** `overdue(at)` answers from what the store holds now about the moment `at`. An item you moved yesterday is read at its new date, whatever moment you ask about; the dates it had before are what `previous_dues` returns. **An item done after the moment was still open at it.** `done_at` is a moment like any other, so an item finished on Wednesday counts as open when you ask about the Tuesday before. That is what makes a past moment answerable. **Unsettled is not overdue.** When two sources give an item two due dates, or two completions, the item has no one place in time. `overdue` and `due_within` leave it out rather than pick one, and `unsettled(at)` names it, so nothing goes missing without a trace. `conflicts()` shows the two values. **Forbidden and blocked are two questions.** `forbidden(at)` asks what you may not do at a moment: during a change freeze it names every open commitment to deploy, whenever each is due. `blocked()` asks which items cannot be done on time without doing what you may not: those due while their action is forbidden. Move a blocked item's due date out of the prohibition's window and it is no longer blocked, while `forbidden` at a moment inside the window still names it. **Forbidden is a report, not a veto.** A commitment to an action a prohibition covers is still a commitment. The module concludes `forbidden` on it for as long as the prohibition applies, and `checkup` names it while it is open; what you do about it is yours. An obligation that is forbidden is the sharper case (you owe what you may not do), and it is reported the same way. **Matching is exact.** A prohibition covers an action written exactly as it is: `"deploy"` does not cover `"deploy the site"`. Write the action the same way in both places, or record one prohibition for each action. --- ## 6. `quantum` — circuits, devices, and what was computed about them `quantum` has two halves that share nothing but the subject. The **simulation** half runs a quantum circuit as values: a state of n qubits is a list of 2ⁿ complex amplitudes, every gate is a function that takes a state and returns the next one, and nothing is written to the store unless you keep a state by name. The **reasoning** half takes a circuit, a device and the results computed about them as facts, and tells you which gates must stay in order, which gates a device cannot run as written and why, which ran on a stale calibration, and whether a result computed outside the store still holds. **Version 1.3.0.** Download it at [dke.langsyn.com/source/quantum/latest/quantum.dpy](https://dke.langsyn.com/source/quantum/latest/quantum.dpy), which always serves the current release, or name the version itself at `https://dke.langsyn.com/source/quantum/1.3.0/quantum.dpy`. **A run has no randomness.** A measurement takes its uniform value as an argument, and two generators supply a sequence of them from a seed you choose, so the same seed gives the same answers every time: `quantum.lcg_next` from a 31-bit seed, and `quantum.chacha_uniform`, ChaCha20 as RFC 8439 defines it, from a 256-bit key. Draw the seed outside the run and pass it in. ### 6.1 Kinds and the fields you write | Kind | Fields you write | |------|------------------| | `quantum.State` | `amplitudes` | | `quantum.Gate` | `op`, `a`, `b`, `step`, `circuit` | | `quantum.Qubit` | `label` | | `quantum.Coupler` | `a`, `b` | | `quantum.Op` | `qubits`, `diagonal` | | `quantum.Device` | `qubits`, `snapshot_at`, `digest` | | `quantum.PhysicalQubit` | `device`, `index`, `t1`, `t2`, `readout_error`, `measured_at` | | `quantum.Native` | `device`, `op`, `a`, `b`, `error`, `length`, `measured_at` | | `quantum.Circuit` | `device`, `digest` | | `quantum.Result` | `circuit`, `digest`, `source`, `source_digest`, `device`, `device_digest`, `producer`, `settings`, `seed`, `outcome` | A **gate** is one operation of a circuit: `op` names the operation, `a` the qubit it acts on and `b` the second qubit of a two-qubit gate, and `step` its position. `circuit` names the circuit it belongs to; the gates that name none are one circuit together. Qubits are named, and a compiled circuit names them by number, as text. A **coupler** is a pair of qubits your chip can act on together, in either order. `Qubit` is for your own reading; no rule reads it. An **op** is an operation's properties: how many qubits it takes, and whether it is diagonal. The module records them for every operation it simulates, and for `id` and `rzz`, when it is imported; record another the same way. A **device** is facts too: its physical qubits, and the operations it runs natively on each qubit and each ordered pair, with each one's measured error, its duration and when it was measured. A **circuit** names the device it was compiled for and the digest of the text it was written from. A **result** is something computed outside the store, such as a compilation, a comparison or the bits a run measured: the circuit it is about, the circuit it came from and the device it was computed for, each with the digest its producer read, and the producer, its settings, its seed and the outcome. You do not have to write device facts or results by hand. dkepy's `dke.quantum.device` writes a device from a calibration snapshot, and `dke.quantum.provenance` writes a result and the digests it names: ``` dke-quantum-device ./torino --max-bytes 40000 > torino.dpy ``` `State` is written by `quantum.save` and read by `quantum.load` (§6.3), never by hand. ### 6.2 Derived and maintained for you | Field | Meaning | |-------|---------| | `quantum.Gate.diagonal` | the gate's operation is diagonal | | `quantum.Gate.track` | the circuit the gate is ordered within: its own, or the empty track for gates that name none | | `quantum.Gate.uncoupled` | a two-qubit gate on no device whose qubits no coupler joins | | `quantum.Gate.device` | the device the gate's circuit was compiled for | | `quantum.Gate.width` | how many qubits the gate's operation takes | | `quantum.Gate.op_on_device` | the gate's device runs its operation somewhere | | `quantum.Gate.first_on_device` / `second_on_device` | the gate's device has its first / second qubit | | `quantum.Gate.off_device` | the gate names a qubit its device does not have | | `quantum.Gate.not_native` | the gate's device runs its operation on no qubit at all | | `quantum.Gate.not_here` | the device runs the operation and has the qubits, but not that operation on these | | `quantum.Gate.reversed` | the device runs the operation on this pair the other way round only | | `quantum.Gate.illegal` | one of the four reasons above holds | | `quantum.Result.circuit_current` / `source_current` | the circuit the result is about / came from has, in the store, the digest the result read | | `quantum.Result.device_current` | the device it was computed for has the digest it read | | `quantum.Result.holds` | the result's outcome, while everything it names is current | | `quantum.Result.unrecorded` | the result omits what it needs to be checked: a circuit, an outcome, or a digest of something it names | | `quantum.Result.circuit_unknown` / `circuit_changed` | the circuit it is about has no digest in the store / a different one | | `quantum.Result.source_unknown` / `source_changed` | likewise for the circuit it came from | | `quantum.Result.device_unknown` | the device it names has no digest in the store | | `quantum.Result.recalibrated` | that device's digest differs from the one it read | | `quantum.Result.stopped` | one of the reasons above holds | A result that names no source, or no device, counts as current on that part. **Two standing constraints** report through `checkup`. `quantum.every_pair_coupled` names each uncoupled gate, and `quantum.every_gate_legal` each gate its device cannot run as written. Neither refuses a write. ### 6.3 Queries Simulation reads and writes no fact. `psi` is a state, `q` a qubit's index (qubit 0 is the lowest bit of an amplitude's index), and `theta` an angle in radians: | Call | Answers | |------|---------| | `quantum.zero(n)` | the all-zero state on `n` qubits | | `quantum.phase(theta)` | e to the i·`theta` | | `quantum.h(psi, q)` | `psi` after a Hadamard gate on `q` | | `quantum.x(psi, q)` | after an X gate | | `quantum.y(psi, q)` | after a Y gate | | `quantum.z(psi, q)` | after a Z gate | | `quantum.s(psi, q)` | after an S gate | | `quantum.t(psi, q)` | after a T gate | | `quantum.sx(psi, q)` | after the square root of X | | `quantum.rx(psi, q, theta)` | after a rotation about X | | `quantum.ry(psi, q, theta)` | after a rotation about Y | | `quantum.rz(psi, q, theta)` | after a rotation about Z | | `quantum.cnot(psi, c, tg)` | after a controlled NOT, control `c`, target `tg` | | `quantum.cz(psi, a, b)` | after a controlled Z | | `quantum.ecr(psi, a, b)` | after an echoed cross-resonance gate, Qiskit's matrix | | `quantum.swap(psi, a, b)` | with qubits `a` and `b` exchanged | | `quantum.ccx(psi, a, b, tg)` | after a Toffoli gate: `tg` flips when `a` and `b` are both 1 | | `quantum.ccz(psi, a, b, c)` | after a gate that negates the amplitudes whose three qubits are all 1 | | `quantum.cswap(psi, c, a, b)` | after a Fredkin gate: `a` and `b` exchanged when `c` is 1 | | `quantum.probabilities(psi)` | the probability of each basis state, in index order | | `quantum.weights(psi, q)` | the probability of qubit `q` being 0, and of its being 1 | | `quantum.collapsed(psi, q, bit, norm)` | the amplitudes whose qubit `q` is `bit`, times `norm`, and zero elsewhere | | `quantum.measure(psi, q, u)` | the outcome, 0 when `u` is below the probability of 0, and the state it leaves | | `quantum.unitary(name, theta)` | the matrix of gate `name` | | `quantum.adjoint(m)` | the conjugate transpose of a matrix | | `quantum.lcg_next(state)` | the next state of a seeded generator, Park and Miller's minimal standard | | `quantum.lcg_uniform(state)` | the generator state as a value in (0, 1) | | `quantum.chacha_quarter(w, a, b, c, d)` | the list `w` after ChaCha20's quarter round on its words `a`, `b`, `c` and `d`, changed in place | | `quantum.chacha_block(key, counter, nonce)` | the 16 words of one ChaCha20 block, for a key of 8 words, a block counter and a nonce of 3 words, each from 0 to 0xFFFFFFFF | | `quantum.chacha_unit(block, i)` | value `i` of a block, from its words 2·`i` and 2·`i` + 1, as an exact 53-bit value in [0, 1) | | `quantum.chacha_uniform(key, n)` | the value at position `n` of the key's stream, from 0 to 2³⁵ − 1: value `n` % 8 of block `n` // 8, with a nonce of zero | A Clifford circuit can run on a stabilizer tableau instead, whose size grows with n² rather than 2ⁿ: | Call | Answers | |------|---------| | `quantum.tableau(n)` | the tableau of the all-zero state on `n` qubits | | `quantum.tab_h(tab, a)` | the tableau after a Hadamard gate | | `quantum.tab_s(tab, a)` | after an S gate | | `quantum.tab_cnot(tab, a, b)` | after a controlled NOT | | `quantum.tab_gate(tab, name, q)` | after any Clifford gate the module names, up to a global phase | | `quantum.tab_measure(tab, a, u)` | the outcome of measuring qubit `a`, and the tableau it leaves | | `quantum.pauli_phase(x1, z1, x2, z2)` | the power of i one Pauli multiplied into another contributes | | `quantum.row_product(hrow, irow, n)` | one tableau row multiplied by another | Two circuits can be compared for equality up to a global phase, which is what a compiler must preserve. A step is a gate's name, its qubits and an angle: | Call | Answers | |------|---------| | `quantum.clifford(n, steps)` | the tableau of a Clifford circuit; two are equal exactly when the circuits are | | `quantum.overlap(n, steps, back, j)` | amplitude `j` after `steps` and then `back` undone, run on basis state `j`; equal circuits answer one phase for every `j` | A circuit of `x`, `cnot`, `ccx`, `swap` and `cswap`, with the signs `z`, `cz` and `ccz`, sends every basis state to one basis state under a sign of +1 or -1, so it can be checked on every input with integers alone, at any width. Qubit `q` of an input is bit `q` of the input's index. Two circuits are equal when, on every input, they give the same output bits under one sign shared by every input: a sign that differs on some inputs only is not equal, and one that differs on all of them is. A circuit meets a specification when, on every input of its input qubits `ins`, those qubits are unchanged, the output qubits `outs` hold the value `want` gives for it (bit `j` of a value on qubit `outs[j]`), every other qubit is 0 again, and the sign is the one `signs` gives (1 for -1; an empty list is +1 for every input). The circuit starts with every qubit but `ins` at 0, and `want` is one value for each of the 2**len(`ins`) inputs, which the caller computes with a function of its own. Both answer `(holds, inputs, first, why)`: the number of inputs checked, the first input that fails or -1, and the reason, which is `"output"` or `"sign"` for two circuits and `"input"`, `"ancilla"`, `"output"` or `"sign"` for a specification, the first that holds at that input. A circuit may also measure. `("mx", [q], 0.0)` measures qubit `q` in the X basis and leaves it 0. Its outcome is not drawn from anything: it is one bit of the case being checked, as a seed is an argument. Under outcome 1 it negates the sign on every input where `q` held 1, and under outcome 0 it changes no sign. A step named `x_if`, `cnot_if`, `ccx_if`, `z_if`, `cz_if`, `ccz_if`, `swap_if` or `cswap_if` is the gate it names, run only where an outcome was 1: it takes the measured qubit first and then the gate's own qubits in the gate's order, so `("cz_if", [t, a, b], 0.0)` is `cz` on `a` and `b` where the latest `mx` of `t` before it gave 1. A step whose first qubit no earlier `mx` measured is refused. The outcomes are numbered by the `mx` steps in step order, outcome `j` being the `j`-th. `rev_meets` holds a circuit over every assignment of them, so a circuit of `M` measurements is checked on 2**`M` times as many cases, and a case `i` is input `i` mod 2**len(`ins`) under the outcomes `i` div 2**len(`ins`); the number it answers is the count of cases, and a check of more than 2**62 cases is refused. With it a qubit that holds the AND of two others, computed by a Toffoli gate, is uncomputed without one: measure it, and where the outcome is 1 apply a `cz` to the two. `rev_same` takes no circuit that measures, since it looks at every input of every qubit, and a measured qubit that starts at 1 is reset and not undone; a circuit that measures is held to a specification, where every qubit but the inputs starts at 0. | Call | Answers | |------|---------| | `quantum.rev_same(n, a, b)` | whether reversible circuits `a` and `b` on `n` qubits are equal, on all 2**`n` inputs; refuses a circuit that measures | | `quantum.rev_meets(n, steps, ins, outs, want, signs)` | whether a reversible circuit, which may measure, meets the specification, on all 2**len(`ins`) inputs under every outcome | | `quantum.rev_code(n, steps)` | the steps of a reversible circuit as `(operation, a, b, c)`, refusing a gate that makes a superposition, a qubit outside the circuit, a qubit named twice in one step and a controlled step whose qubit no `mx` measured | | `quantum.rev_apply(w, code)` | runs what `rev_code` read on wires `w`, 64 inputs to an int, changing them in place; answers the sign of the 64 inputs, every outcome 0 | | `quantum.rev_apply_under(w, code, outcomes)` | `rev_apply` with outcome `j` of the circuit's `mx` steps the bit `j` of `outcomes` | | `quantum.rev_measures(code)` | the number of `mx` steps in what `rev_code` read | | `quantum.lowest_lane(m)` | the lowest bit set in a nonzero 64-bit int | A state can be kept in the store by a name you choose, so a circuit longer than one run is a series of calls that pass nothing but steps. `quantum.save(name, psi)` keeps one, `quantum.drop(name)` forgets it, `quantum.keep(name, b)` keeps bytes `quantum.packed` answered, and `quantum.collapse(name, q, bit, norm)` collapses a kept state; each answers nothing. | Call | Answers | |------|---------| | `quantum.load(name)` | the state kept under `name`, bit for bit, or an empty list | | `quantum.packed(name)` | the bytes kept under `name`, or an empty blob | | `quantum.advance(name, steps, u)` | runs `steps` on the kept state and keeps the result; answers the generator state it ended with and the bits it measured | | `quantum.advance_chacha(name, steps, key, n)` | the same, with its values from `chacha_uniform(key, n)` onward; answers the next position and the bits it measured | | `quantum.weigh(name, q)` | `weights` of the kept state | Over the facts you have loaded: | Call | Answers | |------|---------| | `quantum.after(g)` | every gate that must run after gate `g` | | `quantum.commuting(g)` | every later gate that shares a qubit with `g` and may run before it | | `quantum.uncoupled_gates()` | every two-qubit gate the chip cannot run as written | | `quantum.illegal_gates(c)` | every gate of circuit `c` its device cannot run as written, as `" "`, once for each reason that holds | | `quantum.stale_gates(c, at, max_age)` | every gate of circuit `c` whose native operation was measured after `at`, more than `max_age` before it, or never | | `quantum.results_not_holding()` | every result that does not hold, as `" "`, once for each reason that holds | | `quantum.results_of(c)` | every result that read circuit `c`, as the circuit it is about or the one it came from | | `quantum.produced(r)` | what result `r` records, one `" "` line per field | ```python import quantum def bell(): psi = quantum.zero(2) psi = quantum.h(psi, 0) psi = quantum.cnot(psi, 0, 1) print(quantum.probabilities(psi)) ``` ### 6.4 What the answers can and cannot mean **Equal is up to a global phase.** Two circuits that differ only by a global phase are the same physical operation, and the comparisons treat them as one. `overlap` asks for one phase across every basis state, so a missing `rz` or one of the wrong sign does not pass. **A device is judged by its own facts.** A gate whose circuit names a device is judged by that device's qubits and native operations, and couplers say nothing about it. A circuit naming a device the store holds no facts for has every gate refused, which is also how a misspelt device name shows itself. **Only the 256-bit generator can stand behind a secret.** `lcg_next` has fewer than 2³¹ seeds, so anyone can try every one of them; it is for runs you want to replay. A key for `chacha_uniform` is 8 words from 0 to 0xFFFFFFFF, which `list(struct.unpack("<8I", secrets.token_bytes(32)))` draws in Python. The key is an argument like any other: it travels to the service in the program you send, and the service sees it. A secret that is to stay on your own machine is drawn on your own machine. **The moment is yours to name.** The store keeps no clock, so `stale_gates` takes the moment and the oldest calibration you accept, and asking about that moment again gives the same answer. **A result holds only while what it read is current.** Change a circuit's digest, or load a recalibrated device, and every result that read the old one stops holding, with a reason naming what moved. A result's producer, settings and seed are kept as its recipe and are not judged: no rule can know whether a producer is still current. The store compares digests and never computes one, so a digest is the writer's to keep true. --- ## 7. `pqc` — what to move off vulnerable cryptography first `pqc` reasons about migrating off cryptography a quantum computer breaks. You record where your systems use cryptography, how each algorithm stands, how long what each use protects must stay protected, how long each system takes to migrate, the year you plan against and the deadlines you are held to. It tells you which uses to start on first, which are already exposed, which miss a deadline, and what your inventory does not yet say. **Version 1.0.2.** Download it at [dke.langsyn.com/source/pqc/latest/pqc.dpy](https://dke.langsyn.com/source/pqc/latest/pqc.dpy), which always serves the current release, or name the version itself at `https://dke.langsyn.com/source/pqc/1.0.2/pqc.dpy`. **The reasoning is Mosca's inequality.** A use of a vulnerable algorithm is a problem when the time what it protects must stay protected, plus the time it takes to migrate, reaches past the time a quantum computer can break it. Data recorded now can be decrypted then, so the year to finish is earlier than the horizon by the data's shelf life. ### 7.1 Kinds and the fields you write | Kind | Fields you write | |------|------------------| | `pqc.Horizon` | `year` | | `pqc.Algorithm` | `quantum`, `strength` | | `pqc.Data` | `shelf_life` | | `pqc.System` | `migration` | | `pqc.Deadline` | `year`, `strength` | | `pqc.Use` | `system`, `algorithm`, `protects`, `migrated`; never `horizon`, `shelf_life` or `migration`, which the rules copy onto it | Every year is a calendar year, written as an `int`, and every boundary is the first year WITHOUT something. The **horizon** is the first year you assume a quantum computer able to break the vulnerable algorithms exists. An **algorithm**'s `quantum` is `"vulnerable"` when a quantum computer breaks it, and its `strength` is its classical security strength in bits. **Data**'s `shelf_life` is how many years after it is protected it must stay protected: for a signature, how long it must be trusted. A **system**'s `migration` is how many years moving it off a vulnerable algorithm takes. A **deadline** is the first `year` a vulnerable algorithm of at most `strength` bits may not be used. A **use** is one system using one algorithm to protect one kind of data, and `migrated` is the first year it no longer relies on the algorithm, planned or done; a use with none has no plan. dkepy's `dke.pqc.cbom` writes the algorithms and uses that a cryptographic bill of materials states. The rest are your own facts, recorded beside them: ``` dke-pqc-cbom ./cbom.json --max-bytes 40000 > cbom.dpy ``` ### 7.2 Derived and maintained for you | Field | Meaning | |-------|---------| | `pqc.Use.vulnerable` | the use's algorithm is vulnerable | | `pqc.Use.horizon` / `shelf_life` | the horizon, and the shelf life of what it protects, copied onto a vulnerable use | | `pqc.Use.migration` | its system's migration time, copied onto it | | `pqc.Use.migrate_by` | the first year the use must no longer rely on its algorithm: the horizon less the shelf life | | `pqc.Use.start_by` | the last year to start moving it: `migrate_by` less the migration time | | `pqc.Use.late` | its plan finishes after `migrate_by` | | `pqc.Use.unplanned` | a vulnerable use with no plan | | `pqc.Use.deadline` / `deadline_name` | the earliest deadline covering its algorithm, under any value of a deadline's year, and which deadline that is | | `pqc.Use.misses` | its plan finishes after that deadline | **Two standing constraints** report through `checkup`. `pqc.no_late_plan` names each late use and `pqc.no_missed_deadline` each use that misses its deadline. Neither refuses a write. ### 7.3 Queries Each answers use names. | Call | Answers | |------|---------| | `pqc.priority()` | every vulnerable use with a start year, the earliest first | | `pqc.exposed()` | every late use, and every unplanned use whose migrate-by year is known: what it protects from that year on can be recorded now and read at the horizon | | `pqc.noncompliant()` | every use that misses its deadline, and every unplanned use a deadline covers | | `pqc.remaining(at)` | every vulnerable use still relying on its algorithm in year `at` | | `pqc.unclassified()` | every use with no algorithm, or naming one whose standing is not recorded | | `pqc.unassessed()` | every vulnerable use with no start year: a shelf life, a migration time or a horizon is missing | | `pqc.unsettled()` | every use for which the store holds more than one value of its algorithm, its plan, or the horizon, shelf life or migration time it is reasoned about with | ```python import pqc def plan(): for u in pqc.priority(): print("start: " + u) for u in pqc.noncompliant(): print("misses a deadline: " + u) ``` ### 7.4 What the answers can and cannot mean **The year is yours to name.** The store keeps no clock. Every conclusion is a year, and `remaining` takes the year you ask about, so asking about that year again gives the same answer. **Disagreement is kept, not resolved.** When a scanner and a system's owner record different algorithms, or two policies different shelf lives, every value is reasoned about. A use is late, exposed or remaining if any value on record makes it so, and `priority` places it by the earliest year it must start. A use's deadline is likewise the earliest year of any value of any deadline that covers its algorithm: one source's year, never two added. `unsettled()` names every use where a value of its own, or of the horizon, shelf life or migration time it is reasoned about with, disagrees, so you can settle it. A deadline's year is not among them; `why` on the use's deadline names the year that placed it, beside every year it was chosen from. **An unclassified use is not a safe one.** The rules conclude nothing about a use whose algorithm's standing is not recorded, so it appears in no answer but `unclassified()`. An inventory with one in it is not yet a safe inventory. --- ## 8. `harmony` — four-part writing, and the arithmetic of music `harmony` marks four-part writing as a harmony teacher marks it. You record an exercise's voices and its notes. It names every consecutive fifth and octave between any two voices, every crossed voice, every note outside its voice's range or outside the key, and the reading of every chord from the intervals above its bass. Over a melody's rhythm it checks each bar's total, the notes held across a bar line, and syncopation. Its value scripts calculate equal-tempered frequencies, intervals in cents, set classes, twelve-tone matrices and Euclidean rhythms. **Version 1.0.1.** Download it at [dke.langsyn.com/source/harmony/latest/harmony.dpy](https://dke.langsyn.com/source/harmony/latest/harmony.dpy), which always serves the current release, or name the version itself at `https://dke.langsyn.com/source/harmony/1.0.1/harmony.dpy`. **A pitch is a MIDI note number**: 60 is middle C, and each step is a semitone, so 72 is the C an octave above it. A **beat** is one chord, and an exercise's chords are numbered 1, 2, 3 and so on, in order. A voice's **place** orders the voices from the bottom, 1 for the bass. ### 8.1 Kinds and the fields you write | Kind | Fields you write | |------|------------------| | `harmony.Voice` | `place`, `low`, `high` | | `harmony.Key` | `tonic`, `mode` | | `harmony.Degree` | `key`, `pc` | | `harmony.Note` | `voice`, `beat`, `pitch`, `key`; never `place`, `p1`, `p2`, `p3`, `spacing`, `range`, `chromatic`, `chord` or `seventh`, which the rules conclude | | `harmony.Fifths` | none: a relation the rules conclude between two notes | | `harmony.Octaves` | none: a relation the rules conclude between two notes | | `harmony.Crossing` | none: a relation the rules conclude between two notes | | `harmony.Bar` | `start`, `length`, `pulse`; never `filled`, `miscounted`, `tones`, `claims` or `disputed` | | `harmony.Tone` | `onset`, `duration`; never `barstart`, `barend`, `pulse`, `across` or `syncopated` | A **voice**'s `low` and `high` are its range, as MIDI note numbers. A **key**'s `tonic` is a pitch class, 0 for C up to 11 for B, and its `mode` is `"major"` or `"minor"`; a **degree** is one pitch class that belongs to it. A **note** names its `voice`, its `beat`, its `pitch` and, to be checked against one, its `key`. A **bar** starts at `start` and lasts `length`, counted from the start of the piece in a unit you choose, such as a sixteenth, and `pulse` is the length of its beat; bars of different lengths are how a change of metre is written. A **tone** is a note of a melody, for its rhythm, and a rest is a tone too. Five scripts write these facts for you, under the source `harmony`: - `harmony.satb()` writes the four voices of a chorale, bass to soprano, with the ranges bass E2 to C4, tenor C3 to G4, alto G3 to D5 and soprano C4 to G5. Textbooks differ by a note or two; `update` a range to use another. - `harmony.key(name, tonic, mode)` writes a key and its degrees. A minor key takes its raised sixth and seventh as well as its own degrees. - `harmony.chord(beat, key, bass, tenor, alto, soprano)` writes one chord's four notes, named for their voice and beat: `bass1` to `soprano1` for beat 1. Writing a beat again corrects it. - `harmony.bars(count, length, pulse)` writes `count` bars of one metre, `m1` onward, and `harmony.melody(durations)` a melody's tones end to end, `t1` onward. Writing either again replaces what an earlier call wrote: it forgets the bars or tones it had numbered past the new end. ### 8.2 Derived and maintained for you | Field | Meaning | |-------|---------| | `harmony.Note.place` | the place of the note's voice | | `harmony.Note.p1` / `p2` | the pitch the voices at places 1 and 2 sound on the note's beat | | `harmony.Note.p3` | the pitch the voice at place 3 sounds on the note's beat | | `harmony.Note.pc` | the note's pitch class, `pitch % 12` | | `harmony.Note.nextbeat` | `beat + 1`, which is how a rule reaches the next chord | | `harmony.Note.i1` / `i2` | the note's interval above places 1 and 2 within an octave: 7 is a fifth, and 0 an octave or a unison | | `harmony.Note.i3` | the same above place 3 | | `harmony.Note.d2` / `d3` | the note's full distance in semitones above places 2 and 3 | | `harmony.Note.spacing` | an upper voice more than an octave above the voice below it; the bass may lie further from the tenor | | `harmony.Note.range` | `"low"` or `"high"`, for a note outside its voice's range | | `harmony.Note.chromatic` | a note whose pitch class is no degree of its key | | `harmony.Note.chord` / `seventh` | on a chord's bass note, every reading of the chord as a triad, and as a dominant seventh | | `harmony.Bar.end` | `start + length` | | `harmony.Bar.tones` / `claims` | how many tones starting in the bar have a duration, and how many claims of duration they hold | | `harmony.Bar.disputed` | more claims than tones: a tone is bound to the bar twice, and no `filled` is concluded | | `harmony.Bar.filled` / `miscounted` | how much the tones starting in the bar fill, while it is not disputed, and whether that differs from its length | | `harmony.Tone.barstart` / `barend` | the start and end of the bar the tone starts in | | `harmony.Tone.pulse` | the beat length of that bar | | `harmony.Tone.ends` | `onset + duration` | | `harmony.Tone.offbeat` / `nextpulse` | how far past a beat the tone starts, and where the next beat falls | | `harmony.Tone.across` / `syncopated` | the tone lasts past the end of its bar; it starts off the beat and lasts past the next one | **Three relations** are concluded between two notes on one beat. `harmony.Fifths.a.m` holds when the voice of note `a` and the lower voice of note `m` are a fifth apart on that beat and again on the next, and both move: consecutive fifths, in contrary motion too, with a compound interval counted as its simple form. `harmony.Octaves` is the same for octaves and unisons, and `harmony.Crossing.a.m` holds when `a`'s voice sounds below `m`'s, which is placed under it. **A chord is read from the intervals above its bass.** Major, minor and diminished triads are read in root position and in first and second inversion, a major or minor triad with its fifth left out, and a dominant seventh in root position, with its fifth left out, and in its three inversions. These are the shapes figured bass names: a first inversion is a 6, a second a 6/4, and a seventh's inversions are a 6/5, a 4/3 and a 4/2. ### 8.3 Queries | Call | Answers | |------|---------| | `harmony.faults()` | every fault the rules conclude, one line each, sorted | | `harmony.readings(n)` | every reading of the chord whose bass is note `n` | | `harmony.rhythm_faults()` | every bar whose tones do not fill it exactly, every disputed bar, every tone held across a bar line, and every syncopated tone, one line each, sorted | | `harmony.pairs(kind)` | the pairs of notes, upper then lower, that `"fifths"`, `"octaves"` or `"crossing"` holds between | | `harmony.voice_of(n)` | the voice of note `n`; where sources disagree, every voice on record, joined by `or` | | `harmony.beat_of(n)` | the beat of note `n`; where sources disagree, the earliest | | `harmony.filled_of(b)` | how much of bar `b` its tones fill; where sources disagree, the least; refuses a disputed bar | | `harmony.length_of(b)` | the length of bar `b`; where sources disagree, the least | | `harmony.unsettled()` | every voice, note, bar and tone with a field you write that sources give more than one value, as `note tenor1`, sorted | | `harmony.frequency(midi, a4)` | a note's frequency in equal temperament, A4 at `a4` hertz, 440.0 if not given | | `harmony.cents(ratio)` | an interval's size in cents, 1200 to the octave | | `harmony.beating(f1, h1, f2, h2)` | how many times a second harmonic `h1` of a tone at `f1` hertz beats against harmonic `h2` of one at `f2` | | `harmony.pcset(pcs)` | the distinct pitch classes, sorted | | `harmony.prime_form(pcs, right)` | the set class's prime form, packed from the right, or with `right=False` packed to the left | | `harmony.rotations(s)` | each rotation of a sorted set, transposed to start on 0 | | `harmony.packed_key(r, right)` | what two orderings are compared by under either packing | | `harmony.most_packed(s, right)` | the most packed rotation of a set | | `harmony.interval_vector(pcs)` | how many times each interval class, 1 to 6, occurs in the set | | `harmony.matrix(row)` | a twelve-tone row's matrix, the row on its first line | | `harmony.all_interval(row)` | whether the row's successive intervals are all different | | `harmony.euclid(k, n)` | `k` onsets spread as evenly as they go over `n` steps, by Bjorklund's algorithm, as `x` and `.` | ```python import harmony def mark(): for f in harmony.faults(): print(f) print(harmony.readings("bass1")) ``` ### 8.4 What the answers can and cannot mean **A mark is the rule, not a verdict.** The rules are the ones a teacher of four-part writing marks, written exactly. A note outside the key is marked though it may be a deliberate chromatic note, such as the raised fourth of a secondary dominant, and consecutive fifths are marked whatever the style of the piece. What the module does not check is listed rather than implied: hidden fifths and octaves, doubling, the resolution of a leading note or a seventh, melodic intervals, and more than four voices. **A chord takes every reading inside it.** A rule reads exactly the intervals it names, so a dominant seventh is read as a major triad as well, a chord with both a major and a minor third is read as both, and a chord of no shape the rules know, such as an augmented triad, has no reading. **A correction replaces; a disagreement is kept.** `chord()` writes under the source `harmony`, so writing a beat again replaces its notes and withdraws every mark they no longer support. A note written under another source is held beside the first rather than replacing it. Every mark it reaches is drawn from both values, a line of `faults()` names it with each voice and beat it has, and `harmony.unsettled()` names it. The one exception is a mark that asks for something to be missing: a note is outside the key only when no value of its pitch is in the key, and a chord is read with its fifth omitted only when no value supplies the fifth. Only `filled_of` refuses, and only a disputed bar. **A bar's fill is not drawn from a tone bound to it twice.** How much a bar's tones fill is a sum, and a sum adds every value it reads: a tone whose duration a second source writes, even the same duration, would be counted twice, and the bar would read as overfilled where every source fills it. So while a tone is bound to its bar twice, the bar is `disputed`: no `filled` and no `miscounted` is concluded for it, `rhythm_faults()` names it as `bar m1: disputed`, `filled_of` refuses it, and `why_not` on its `filled` names the dispute. A tone with no duration fills nothing and hides no dispute. Forgetting the claim that is not to stand settles it. A tone whose onset two sources disagree on starts in each bar a value puts it in, so each of those fills is one source's. **The calculations are exact.** Each value script computes what the same formula computes in CPython, to the last bit. Prime forms are given under both published criteria, which disagree for six set classes. --- ## 9. `physics` — units and dimensions, and classical motion `physics` checks quantities as an engineer checks them. You record each quantity as a value in a unit, and the units with their dimensions. It concludes every quantity's value in SI, and names a quantity handed over in a unit other than the one its reader expects, as a pound-force second where a newton second is expected. It judges whether an equation written as a product of quantities is dimensionally consistent, and says what is left over when it is not. It adds the resistance of resistors joined in series or in parallel. Its value scripts calculate a pendulum's period by integration and exactly, an orbit under three integrators, the heat equation, a linear system by Gaussian elimination, the current through a bridge circuit's meter, and the Ising model of a magnet. **Version 1.0.3.** Download it at [dke.langsyn.com/source/physics/latest/physics.dpy](https://dke.langsyn.com/source/physics/latest/physics.dpy), which always serves the current release, or name the version itself at `https://dke.langsyn.com/source/physics/1.0.3/physics.dpy`. **A unit has a dimension and a factor.** The dimension is its powers of mass, length, time, electric current and temperature: a newton is mass 1, length 1 and time -2. The factor is what one of the unit is in the SI unit of that dimension. The module holds no units until you ask: `physics.si_units()` writes a list of common ones, each with the source of its value, and a unit not in it is written with `physics.unit`. ### 9.1 Kinds and the fields you write | Kind | Fields you write | |------|------------------| | `physics.Unit` | `mass`, `length`, `time`, `current`, `temperature`, `factor` | | `physics.Quantity` | `value`, `unit`, `expects`; never `factor`, `mass`, `length`, `time`, `current`, `temperature`, `efactor`, `emass`, `elength`, `etime`, `ecurrent`, `etemperature`, `unknown`, `unexpected` or `handoff`, which the rules conclude | | `physics.Factor` | `equation`, `quantity`, `power`; never `mass`, `length`, `time`, `current`, `temperature` or `unknown`, which the rules conclude | | `physics.Equation` | none: its sums `mass`, `length`, `time`, `current` and `temperature`, and `consistent` and `unbalanced`, are concluded from its factors, and so are `mass_factors`, `length_factors`, `time_factors`, `current_factors`, `temperature_factors`, `mass_claims`, `length_claims`, `time_claims`, `current_claims`, `temperature_claims`, `mass_disputed`, `length_disputed`, `time_disputed`, `current_disputed` and `temperature_disputed` | | `physics.Resistor` | `ohms`, `group` | | `physics.Group` | `joined`; never `ohms`, `siemens`, `ohms_resistors`, `ohms_claims`, `ohms_disputed`, `siemens_resistors`, `siemens_claims` or `siemens_disputed` | A **unit** is named by letters, digits and underscores, such as `"N"`, `"lbf_s"` or `"m_per_s2"`. A unit whose zero is not SI's, such as a degree Celsius, is not a factor and is not written. A **quantity** is a value in a unit, by a name you choose; its `expects` is the unit its reader takes it in, where it is handed from one system to another. A **factor** is one quantity in an equation written as a product of powers. Write every power on the left side positive and every power on the right side negative, so `F = m a` is `F` to the 1, `m` to the -1 and `a` to the -1. A pure number such as the 1/2 of `E = m v²/2` has no dimension and needs no factor, and a square root is written by doubling every power in the equation. A **resistor** names its resistance in ohms and the **group** it is joined in, `"series"` or `"parallel"`. Eight scripts write these facts for you: - `physics.si_units()` writes the list of units, each under the source that gives its value: the SI Brochure (9th edition, 2019) for the SI units and those defined by them, and NIST Special Publication 811 for the inch, foot, mile, miles per hour, pound, pound-force, pound-force second, foot-pound, pound per square inch, atmosphere and calorie. A pound is 0.45359237 kg and a pound-force a pound under standard gravity, 9.80665 m/s². - `physics.unit(name, mass, length, time, current, temperature, factor, source)` writes one unit under the source you give. - `physics.quantity(name, value, unit)` writes a quantity, and `physics.expect(name, unit)` the unit its reader takes it in. Writing a name again corrects it. - `physics.factor(equation, quantity, power)` writes one factor of an equation. - `physics.resistor(name, ohms, group)` and `physics.group(name, joined)` write a resistor and the group it is joined in. ### 9.2 Derived and maintained for you | Field | Meaning | |-------|---------| | `physics.Quantity.factor` | the factor of the quantity's unit | | `physics.Quantity.mass` / `length` | the powers of mass and length in the quantity's unit | | `physics.Quantity.time` / `current` | the powers of time and current in the quantity's unit | | `physics.Quantity.temperature` | the power of temperature in the quantity's unit | | `physics.Quantity.si` | `value * factor`: the quantity's value in SI | | `physics.Quantity.efactor` | the factor of the unit the quantity expects | | `physics.Quantity.emass` / `elength` | the powers of mass and length in the unit the quantity expects | | `physics.Quantity.etime` / `ecurrent` | the powers of time and current in the unit the quantity expects | | `physics.Quantity.etemperature` | the power of temperature in the unit the quantity expects | | `physics.Quantity.converted` | `value * (factor / efactor)`: the value in the unit its reader expects, the ratio of the factors taken first so that a value expected in its own unit comes back as it was; a real wherever that value is one, for factors from 10^-150 to 10^150 | | `physics.Quantity.unknown` | the quantity's unit is not in the list | | `physics.Quantity.unexpected` | the unit the quantity expects is not in the list | | `physics.Quantity.handoff` | `"convert"` when the expected unit has the same dimension and another unit, `"incompatible"` when it has another dimension | | `physics.Factor.mass` / `length` | the powers of mass and length in its quantity's unit | | `physics.Factor.time` / `current` | the powers of time and current in its quantity's unit | | `physics.Factor.temperature` | the power of temperature in its quantity's unit | | `physics.Factor.smass` / `slength` | the factor's share of mass and of length: its `power` times that power | | `physics.Factor.stime` / `scurrent` | the factor's share of time and of current | | `physics.Factor.stemperature` | the factor's share of temperature | | `physics.Factor.unknown` | the factor's quantity has no unit in the list | | `physics.Equation.mass_factors` / `length_factors` | how many of its factors hold a share of mass, and of length | | `physics.Equation.time_factors` / `current_factors` | how many hold a share of time, and of current | | `physics.Equation.temperature_factors` | how many hold a share of temperature | | `physics.Equation.mass_claims` / `length_claims` | how many claims of their shares of mass, and of length, those factors hold | | `physics.Equation.time_claims` / `current_claims` | the same of time, and of current | | `physics.Equation.temperature_claims` | the same of temperature | | `physics.Equation.mass_disputed` / `length_disputed` | more claims than factors: a factor is bound to the equation twice by its share of mass, or of length, and that sum is not concluded | | `physics.Equation.time_disputed` / `current_disputed` | the same of time, and of current | | `physics.Equation.temperature_disputed` | the same of temperature | | `physics.Equation.mass` / `length` | the sum of its factors' shares of mass, and of length, while that dimension is not disputed | | `physics.Equation.time` / `current` | the sum of its factors' shares of time, and of current, while that dimension is not disputed | | `physics.Equation.temperature` | the sum of its factors' shares of temperature, while it is not disputed; all five sums are 0 when the equation is consistent | | `physics.Equation.consistent` | all five sums are 0 and no factor is unknown | | `physics.Equation.unbalanced` | a sum is not 0 and no factor is unknown | | `physics.Resistor.siemens` | `1.0 / ohms`: the resistor's conductance | | `physics.Group.ohms_resistors` / `ohms_claims` | for a series group, how many resistors carry a resistance into its sum, and how many claims of it they hold | | `physics.Group.siemens_resistors` / `siemens_claims` | for a parallel group, how many carry a conductance into its sum, and how many claims of it they hold | | `physics.Group.ohms_disputed` / `siemens_disputed` | more claims than resistors: a resistor is bound to the group twice, and that sum is not concluded | | `physics.Group.ohms` | for a series group, the sum of its resistors' resistances, while it is not disputed | | `physics.Group.siemens` | for a parallel group, the sum of its resistors' conductances, while it is not disputed | | `physics.Group.parallel` | `1.0 / siemens`: a parallel group's resistance | A **hand-over** is a quantity written in one unit that its reader takes in another. If both have one dimension, the value is converted and `converted` reads it in the expected unit. If they have different dimensions, no conversion makes it the quantity the reader wants: a mass in kilograms where a force in newtons is expected is marked `"incompatible"`. A quantity given in the unit it expects is no hand-over and is marked nothing. ### 9.3 Queries | Call | Answers | |------|---------| | `physics.checks()` | every unknown unit, unbalanced equation, hand-over, disputed equation and disputed group the rules conclude, one line each, sorted | | `physics.si(name)` | the quantity's value in SI; where sources disagree, the least; 0.0 when there is none | | `physics.converted(name)` | the quantity's value in the unit its reader expects; where sources disagree, the least; 0.0 when there is none | | `physics.dimension(name)` | what is left over of an equation's dimensions, as `L1 T-1` for `E = m v`; `consistent` when nothing is, `unknown unit` when a factor's unit is not recorded, `no factors` when it has none and `disputed` when a dimension's sum is | | `physics.resistance(name)` | a group's resistance in ohms, series or parallel; 0.0 when there is none; refuses a disputed group | | `physics.disputed_equations()` | every equation a factor is bound to twice by its share of some dimension, sorted | | `physics.disputed_groups()` | every group a resistor is bound to twice, sorted | | `physics.unsettled()` | every unit, quantity, factor, group and resistor with a field you write that sources give two different values, as `quantity x`, sorted | | `physics.small_period(length, g)` | a pendulum's period through a small angle, 2π√(L/g), the same for every amplitude | | `physics.agm(a, b)` | the arithmetic–geometric mean of two numbers not below 0: their mean and geometric mean taken again and again until the two agree | | `physics.period(theta0, length, g)` | the exact period of a pendulum released at rest from `theta0` radians, from the arithmetic–geometric mean | | `physics.pendulum(theta0, length, g, dt)` | the same period by integrating the equation of motion with the fourth-order Runge–Kutta method at steps of `dt` seconds | | `physics.orbit(e, method, periods, steps)` | the relative error in energy and in angular momentum, and the distance from the start, after a Kepler orbit of eccentricity `e` stepped by `"euler"`, `"verlet"` or `"rk4"` | | `physics.heat(cells, r, steps)` | the temperature at the middle of a rod after `steps` steps of forward differences in time and centred differences in space | | `physics.heat_exact(time)` | the same temperature at `time` from Fourier's series | | `physics.solve(a, b)` | the solution of `a · x = b` by Gaussian elimination with partial pivoting; `[]` when the system is singular | | `physics.bridge(volts, r1, r2, r3, r4, rg)` | the current in amperes through the meter of a Wheatstone bridge; 0 when it balances | | `physics.ising(n, temperature, sweeps, seed)` | the mean magnetisation per spin of an `n` by `n` magnet over the second half of its sweeps, the same for the same seed | ```python import physics physics.si_units() physics.quantity("thrust", 5.0, "lbf") physics.expect("thrust", "N") physics.quantity("mass", 2.0, "kg") physics.expect("mass", "N") print(physics.checks()) print(physics.converted("thrust")) print(physics.si("mass")) ``` prints `['handed over: mass in kg where N is expected, incompatible', 'handed over: thrust in lbf where N is expected, convert']`, `22.2411080763025` and `2.0`. ### 9.4 What the answers can and cannot mean **A consistent equation is not a true one.** `consistent` says the dimensions on both sides are the same, and `E = m v²` and `E = 2 m v²` are equally consistent. An unbalanced equation is wrong, and `dimension` says by how much: `E = m v` leaves `L1 T-1`. What the module does not do is solve an equation, or decide whether a quantity is plausible. **A unit is only as good as the list.** A quantity in a unit the list does not hold has no `si` and no dimension, and `checks()` names it. An equation with such a factor is judged neither consistent nor unbalanced, and `dimension` reads `unknown unit`. The list written by `si_units()` is the units it lists; there is no degree Celsius, whose zero is not SI's, and no mole or candela. A unit you add with `physics.unit` is as good as the factor you give it. **A circuit of series and parallel groups is a sum.** `physics.resistance` adds and reciprocates; a circuit that is not built of series and parallel groups, such as a bridge, is solved with `physics.solve` or `physics.bridge`. A group's members are resistors, not groups, so a group inside a group is not added. **A correction replaces; a disagreement is kept.** The writing scripts write under the source `physics`, so writing a quantity or a resistor again replaces its value and withdraws every conclusion that rested on the old one. A value written under another source is held beside the first. Every conclusion it reaches is drawn from both values, and `physics.unsettled()` names the fact. **A sum is not drawn from a member bound to it twice.** An equation's dimensions and a group's resistance are sums, and a sum adds every value it reads: a resistor whose `ohms` a second source writes, even the same `ohms`, would be counted twice, 400 ohms where every source says 300. So while a member is bound to its sum twice, that sum is disputed and not concluded, and neither is what rests on it: `dimension` reads `disputed`, `resistance` refuses the group, `checks()` names each, `disputed_equations()` and `disputed_groups()` list them, and `why_not` on the sum names the dispute. A share both powers of a factor agree on, as every share of a dimensionless unit does, disputes nothing, and a parallel group is disputed only by a resistance that differs, since an equal one is one conductance. A member with no value of the sum, such as a factor in a unit not in the list or a resistor of 0 ohms in a parallel group, adds nothing and hides no dispute. Forgetting the claim that is not to stand settles it. A factor whose equation, or a resistor whose group, two sources disagree on counts in each a value names, so each of those sums is one source's. No other query refuses. **The calculations are exact in the sense that matters.** Each computes what the same formula computes in CPython. The integrators are what they are called: the Euler orbit of eccentricity 0.5 drifts a long way in ten periods and the fourth-order Runge–Kutta one hardly at all. A value script has no inputs but its arguments and reads nothing from the store. A calculation checks its arguments only where a refusal is the answer, with `ValueError`: `small_period` and `period` refuse a length below 0 or a `g` not above 0, `period` an amplitude outside -π to π, `pendulum` an amplitude not above 0 or above π and a length or a step not above 0, `agm` a number below 0, `orbit` an eccentricity outside 0 to below 1, and `bridge` a resistance not above 0. `pendulum` integrates for up to the exact period, so a pendulum released near the top is timed rather than answered with 0.0, as 1.0.2 did, and refuses a step too long to reach the vertical. `solve` reads a system as singular only where a pivot is exactly 0, as `mathematics.solve` does, where 1.0.2 read any pivot under 1e-300 so. Where a step of a value script's work would pass the largest real or fall below the smallest normal one while its value would not, the script works it so that it stays in range, which changes no digit of an answer whose work stays in range. --- ## 10. `chemistry` — molecular structure, reaction enthalpy, and the arithmetic of reactions `chemistry` reads a molecule as a chemist reads a skeletal drawing. You record a molecule's atoms and the bonds between them. It concludes the hydrogens each atom carries, each molecule's formula and weight, and the functional groups on its atoms: acids, esters, amides, aldehydes, ketones, alcohols and amines. You record a reaction as species with coefficients and each species' enthalpy of formation, and it concludes the reaction's enthalpy. Its value scripts calculate a formula's molar mass, the balance of an equation in exact integers, the pH of an acid and a titration curve, and the kinetics of consecutive and of reversible reactions. **Version 1.0.3.** Download it at [dke.langsyn.com/source/chemistry/latest/chemistry.dpy](https://dke.langsyn.com/source/chemistry/latest/chemistry.dpy), which always serves the current release, or name the version itself at `https://dke.langsyn.com/source/chemistry/1.0.3/chemistry.dpy`. **Hydrogens are not written.** A molecule is its heavy atoms and the bonds between them. Each atom carries the hydrogens its valence leaves over, so an oxygen with one bond carries one hydrogen and an atom with no bond carries its whole valence. The valences and weights are those of the elements in the store: `chemistry.elements()` writes thirteen of them, and you write an `Element` yourself for any other. ### 10.1 Kinds and the fields you write | Kind | Fields you write | |------|------------------| | `chemistry.Element` | `z`, `valence`, `weight` | | `chemistry.Atom` | `element`, `molecule`; never `valence`, `weight`, `bond_halves`, `bond_claims`, `bonds_disputed`, `bonds`, `hweight`, `overbonded`, `carbonyl` or `group`, which the rules conclude | | `chemistry.Bond` | `a`, `b`, `order`; never `belement`, `bhydrogens` or `bcarbonyl` | | `chemistry.Molecule` | none: named by its atoms; never `weight_atoms`, `weight_claims`, `weight_disputed`, `hydrogen_atoms`, `hydrogen_claims`, `hydrogens_disputed`, `weight`, `hydrogens` or `heavy` | | `chemistry.Species` | `formation` | | `chemistry.Term` | `reaction`, `species`, `coefficient`; never `formation` or `missing` | | `chemistry.Reaction` | none: named by its terms; never `terms`, `claims`, `disputed`, `enthalpy` or `incomplete` | An **element** is named by its symbol: its atomic number `z`, its `valence`, the number of bonds it forms in an organic molecule, which is 0 for an element that forms none there, and its standard atomic `weight`. An **atom** is named by you, and gives its element's symbol and the molecule it belongs to. A **bond** goes from atom `a` to atom `b` with an `order` of 1, 2 or 3; a bond is written as its two halves, one from each atom, so each reaches the other. A **species** is a substance in a state, such as `"water_l"` for liquid water, with its standard enthalpy of formation in kJ/mol. A **term** is one species of a reaction with its coefficient, positive for a product and negative for a reactant. Six scripts write these facts for you: - `chemistry.elements()` writes the elements the rules read: H, C, N, O, Na, P, S, Cl, K, Ca, Cr, Mn and Fe, with the abridged standard atomic weights of IUPAC's Commission on Isotopic Abundances and Atomic Weights and each element's usual valence in an organic molecule. Call it once before reading a structure. `chemistry.element(symbol, z, valence, weight)` writes one. - `chemistry.atom(name, symbol, molecule)` writes an atom. - `chemistry.bond(a, b, order)` writes both halves of a bond, named `a-b` and `b-a`. Writing a bond again corrects it. - `chemistry.species(name, formation)` writes a species and its enthalpy of formation in kJ/mol. - `chemistry.term(reaction, species, coefficient)` writes one species of a reaction, named `reaction`, `-` and `species`. ### 10.2 Derived and maintained for you | Field | Meaning | |-------|---------| | `chemistry.Atom.valence` | the valence of the atom's element; absent for an element whose valence is 0 | | `chemistry.Atom.weight` | the standard atomic weight of the atom's element | | `chemistry.Atom.hweight` | the weight of hydrogen, the element of atomic number 1 | | `chemistry.Atom.bond_halves` / `bond_claims` | how many halves of its bonds carry an order, and how many claims of order they hold | | `chemistry.Atom.bonds_disputed` | more claims than halves: a bond is bound to the atom twice, and no `bonds` is concluded, nor its hydrogens or mass | | `chemistry.Atom.bonds` | the sum of the orders of the atom's bonds, while they are not disputed; 0 for an atom with none | | `chemistry.Atom.hydrogens` | `valence - bonds`: the hydrogens the atom carries | | `chemistry.Atom.mass` | `weight + hydrogens * hweight`: the atom's weight with its hydrogens | | `chemistry.Atom.overbonded` | the atom is bonded past its valence, so its hydrogens are fewer than 0 | | `chemistry.Atom.carbonyl` | a carbon with a double bond to an oxygen | | `chemistry.Atom.group` | the functional group the atom carries: `"carboxylic acid"`, `"ester"`, `"amide"`, `"aldehyde"` or `"ketone"` on a carbonyl carbon, `"alcohol"` on an oxygen, `"amine"` on a nitrogen | | `chemistry.Bond.belement` | the element at the far end of the bond | | `chemistry.Bond.bhydrogens` | the hydrogens of the atom at the far end | | `chemistry.Bond.bcarbonyl` | the atom at the far end is a carbonyl carbon | | `chemistry.Molecule.weight_atoms` / `weight_claims` | how many of its atoms have a mass, and how many claims of mass they hold | | `chemistry.Molecule.hydrogen_atoms` / `hydrogen_claims` | how many have hydrogens, and how many claims of them they hold | | `chemistry.Molecule.weight_disputed` / `hydrogens_disputed` | more claims than atoms, or an atom of it whose bonds are disputed: that sum is not concluded | | `chemistry.Molecule.weight` | the sum of its atoms' masses, in g/mol, while it is not disputed | | `chemistry.Molecule.hydrogens` | the sum of its atoms' hydrogens, while they are not disputed | | `chemistry.Molecule.heavy` | how many atoms it has that are not hydrogens: the atoms you wrote, each counted once | | `chemistry.Term.formation` | the enthalpy of formation of the term's species | | `chemistry.Term.contribution` | `coefficient * formation` | | `chemistry.Term.missing` | the term's species has no enthalpy of formation recorded | | `chemistry.Reaction.terms` / `claims` | how many of its terms have a contribution, and how many claims of it they hold | | `chemistry.Reaction.disputed` | more claims than terms: a term is bound to the reaction twice, and no enthalpy is concluded | | `chemistry.Reaction.enthalpy` | the sum of its terms' contributions, in kJ, while it is not disputed | | `chemistry.Reaction.incomplete` | a term of the reaction is `missing` | **Groups are read from the bonds.** A carbonyl carbon with a single bond to an oxygen that carries a hydrogen is a carboxylic acid; to an oxygen that carries none, an ester; to a nitrogen, an amide. A carbonyl carbon that carries a hydrogen and has no single bond to an oxygen or a nitrogen is an aldehyde, and one with single bonds to two different carbons is a ketone. An oxygen with one hydrogen bonded to a carbon that is not a carbonyl carbon is an alcohol, and a nitrogen bonded to carbon, none of it a carbonyl carbon, is an amine. The group is concluded on the atom that carries it. **A reaction's enthalpy is Hess's law.** The enthalpy of a reaction is the sum, over its species, of each coefficient times that species' enthalpy of formation, whatever path the reaction takes. ### 10.3 Queries | Call | Answers | |------|---------| | `chemistry.weight(name)` | the weight of molecule `name` in g/mol; 0.0 when it has none; refuses a molecule whose weight is disputed | | `chemistry.enthalpy(reaction)` | the enthalpy of reaction `reaction` in kJ; 0.0 when it has none; refuses a disputed reaction | | `chemistry.formula(name)` | a molecule's formula in the Hill order: carbon, then hydrogen, then the other elements alphabetically, or every element alphabetically when there is no carbon; each atom counted once; refuses a molecule whose hydrogens are disputed or one of whose atoms holds two elements | | `chemistry.disputed_molecules()` | every molecule whose weight or hydrogens are disputed, sorted | | `chemistry.disputed_reactions()` | every reaction a term is bound to twice, sorted | | `chemistry.groups(name)` | the functional groups on a molecule's atoms, each named once, sorted | | `chemistry.faults()` | every atom bonded past its valence, as `overbonded: ` and the atom, every reaction with a species whose enthalpy of formation is not recorded, as `incomplete: ` and the reaction, and every disputed atom, molecule and reaction, as `disputed: atom ` and the atom and so on, sorted | | `chemistry.unsettled()` | every element, atom, bond, species and term with a field you write that sources give two different values, as `element S`, sorted | | `chemistry.parse(formula)` | how many atoms of each element a formula holds, as a dict: `"Ca(OH)2"` is one calcium, two oxygens and two hydrogens; brackets nest; a charge after a caret is left out | | `chemistry.charge(formula)` | the charge after a caret: `"MnO4^-"` is -1, `"Fe^3+"` is 3, no caret is 0 | | `chemistry.molar_mass(formula)` | a formula's molar mass in g/mol from the elements' weights in the store; 0.0 when an element has none | | `chemistry.gcd(a, b)` | the greatest common divisor of the absolute values of two integers | | `chemistry.balance(reactants, products)` | the least whole-number coefficients that balance an equation, reactants first, every element and the charge the same on both sides; `[]` unless there is exactly one balance up to scale with every coefficient positive | | `chemistry.charge_ph(acid, base, pkas)` | the pH at which the charges balance: a weak acid of total concentration `acid` mol/L with the acid constants `pkas`, one for each proton, beside `base` mol/L of a strong base's cation | | `chemistry.ph(acid, pkas)` | the pH of a weak acid of concentration `acid` mol/L in water | | `chemistry.titration(acid, va, base, vb, pkas)` | the pH after `vb` mL of a strong base of `base` mol/L is added to `va` mL of a weak acid of `acid` mol/L | | `chemistry.consecutive(k1, k2, t, dt)` | the fractions of A, B and C at time `t` for A to B to C, first order, from pure A, integrated by the fourth-order Runge-Kutta method in steps of `dt` | | `chemistry.consecutive_exact(k1, k2, t)` | the same fractions in closed form, for `k1` different from `k2` | | `chemistry.peak_time(k1, k2)` | when B is at its most: `ln(k1 / k2) / (k1 - k2)`, and `1 / k1` when the two are equal | | `chemistry.equilibrium(kf, kr, a0, b0, t, dt)` | the concentration of C at time `t` for A plus B to C and back, from `a0` and `b0` mol/L of A and B and no C, integrated in steps of `dt` | | `chemistry.equilibrium_exact(k, a0, b0)` | the concentration of C at equilibrium, where C over A times B equals `k`, which is `kf / kr` | ```python import chemistry chemistry.elements() chemistry.atom("a1", "C", "methanol") chemistry.atom("a2", "O", "methanol") chemistry.bond("a1", "a2", 1) for g in chemistry.groups("methanol"): print(g) print(chemistry.formula("methanol")) print(chemistry.faults()) print(chemistry.unsettled()) ``` prints `alcohol`, `CH4O`, `[]` and `[]`: the oxygen carries one hydrogen and is bonded to a carbon that is no carbonyl carbon, the carbon carries three, and nothing is overbonded or in dispute. ### 10.4 What the answers can and cannot mean **A structure is its bonds and nothing more.** The rules read atoms, bond orders and the one valence each element has in the store. They do not represent charge, rings, aromaticity or stereochemistry, a second valence for an element such as sulfur or phosphorus, or any group beyond the seven named above. A nitrogen is read as an amine only while every bond it has is single, so a nitrile's or an imine's nitrogen carries no group, and an ether, which carries no group the rules name, has none. **Nothing is concluded without the elements.** Before `chemistry.elements()` is called, or for an atom whose symbol has no `Element`, an atom has no valence and so no hydrogens, and its molecule's formula has none of its hydrogens. An element of valence 0, such as sodium or iron, gives its atoms no hydrogens whatever their bonds, and its weight: a sodium bonded to a chlorine reads `ClNa` and weighs 58.44. **A correction replaces; a disagreement is kept.** `atom()`, `bond()`, `species()` and `term()` write under the source `chemistry`, so writing a bond again replaces it and withdraws every group and weight that rested on the old one. A fact written under another source is held beside the first. Every conclusion reached from it is drawn from both values, and `chemistry.unsettled()` names it. **A sum is not drawn from a member bound to it twice.** An atom's bonds, a molecule's weight and hydrogens and a reaction's enthalpy are sums, and a sum adds every value it reads: a sulfur atom whose element's weight two sources give as 32.06 and 32.065 would be weighed at both, and a bond whose order a second source writes, even the same order, counted twice. So while a member is bound to its sum twice, that sum is disputed and not concluded, and neither is what rests on it. An atom whose bonds are disputed has no hydrogens or mass, so its molecule is disputed too rather than weighed without it. `weight()` refuses a molecule whose weight is disputed, `formula()` one whose hydrogens are, and `enthalpy()` a disputed reaction; `disputed_molecules()`, `disputed_reactions()` and `faults()` name them, and `why_not` on the sum names the dispute. A sum both sources agree on stands: the sulfane's formula reads `H2S` while its weight is disputed. Forgetting the claim that is not to stand settles it. An atom whose molecule, a bond whose atom, or a term whose reaction two sources disagree on counts in each a value names, so each of those sums is one source's. **A formula counts each atom once.** An atom two sources both give as oxygen is one oxygen. One they give as oxygen and as sulfur is refused by `formula()`, which names the atom, until one element is forgotten. **An incomplete reaction is a partial sum.** A term whose species has no enthalpy of formation adds nothing, so `chemistry.enthalpy()` answers the sum of the terms that have one, and `chemistry.faults()` names the reaction as `incomplete`. **A bond past its valence is marked, not refused.** An atom bonded past its valence is `overbonded` and carries fewer than 0 hydrogens, which lowers its molecule's hydrogens and so its formula. `chemistry.faults()` names it. **`balance` answers only when the equation has one balance.** An equation that balances in more than one independent way, or in none, answers `[]`, as does one whose balance needs a coefficient of 0 or less. The coefficients are exact integers, found without rounding. **The pH and kinetics are calculations, not measurements.** `chemistry.ph()` takes water's ion product as 1.0e-14 and ignores ionic strength. The Runge-Kutta scripts approach their closed forms as `dt` shrinks. A calculation checks its arguments only where a refusal is the answer, with `ValueError`: `charge_ph`, `ph` and `titration` refuse a concentration or a volume below 0, the kinetics scripts a rate constant or a time below 0, `consecutive` a step not above 0 and one so long for the rates that the integration diverges, and `equilibrium_exact` a constant or a concentration below 0. `equilibrium_exact` takes the smaller root as `2 k a0 b0 / (p + r)`, which subtracts nothing, where 1.0.2's `(p - r) / 2k` lost digits to cancellation, all of them for a small constant: at constants from 1e-3 to 1e3 it is within 3 units in the last place of the exact root, where 1.0.2 was off by a median of 5 and up to 89 million. Where a step of a value script's work would pass the largest real or fall below the smallest normal one while its value would not, the script works it so that it stays in range, which changes no digit of an answer whose work stays in range. --- ## 11. `biology` — networks, pedigrees and the arithmetic of inheritance `biology` reads two kinds of record you write as facts. From a metabolic network, a growth medium and the reactions that connect compounds, it concludes which reactions can run and which compounds the network can make, which is its scope, and it asks which one compound added to the medium would let the network make one it cannot. From a pedigree, the people of a family and the mode their trait is inherited by, it concludes every person's ancestors, each child whose parents are related by descent, the carriers the mode requires and each person whose record the mode cannot explain. Its value scripts calculate what genetics calculates: transcription and translation, a Mendelian cross and the chi-square test of its counts, Hardy–Weinberg proportions, logistic growth, predator and prey, genetic drift and a ring of genes that repress one another. **Version 1.0.2.** Download it at [dke.langsyn.com/source/biology/latest/biology.dpy](https://dke.langsyn.com/source/biology/latest/biology.dpy), which always serves the current release, or name the version itself at `https://dke.langsyn.com/source/biology/1.0.2/biology.dpy`. **A network is facts about names.** A reaction names its substrates and its products, each one or two, and a compound is any name a reaction or the medium mentions. A reaction **runs** when every substrate is **reached**, and a compound is reached when the medium supplies it or a reaction that runs makes it. The **scope** is every compound reached. **A mode** is how the trait a family is recorded for is inherited: `"autosomal dominant"`, `"autosomal recessive"` or `"X-linked recessive"`. ### 11.1 Kinds and the fields you write | Kind | Fields you write | |------|------------------| | `biology.Compound` | `medium`; never `reached`, which the rules conclude | | `biology.Reaction` | `s1`, `s2`, `p1`, `p2`; never `runs` | | `biology.Family` | `mode`; never `unexplained` | | `biology.Person` | `family`, `mother`, `father`, `sex`, `affected`; never `inbred`, `carrier` or `unexplained` | | `biology.Parent` | none: a relation the rules conclude between a child and a parent | | `biology.Ancestor` | none: a relation the rules conclude between a person and an ancestor | A **compound**'s `medium` is True for one the growth medium supplies. A **reaction**'s `s1` and `s2` are its substrates and `p1` and `p2` its products, each the name of a compound; leave `s2` or `p2` unwritten for a reaction with one. A **family** has a `mode`. A **person** names its `family`, its `mother` and `father` where known, its `sex`, `"F"` or `"M"`, and whether it is `affected`. Four scripts write these facts for you: - `biology.medium(name)` marks a compound the medium supplies, under the source `medium`. - `biology.reaction(name, s1, s2, p1, p2)` writes a reaction under the source `network`. Pass `""` for a second substrate or product the reaction does not have. - `biology.family(name, mode)` writes a family, and `biology.person(name, family, sex, affected, mother, father)` a person, under the source `pedigree`. Pass `""` for a parent not recorded. Writing a name again corrects it. Passing `""` writes nothing, so it does not withdraw a second substrate, a second product or a parent an earlier call recorded: write the name you mean instead. ### 11.2 Derived and maintained for you | Field | Meaning | |-------|---------| | `biology.Compound.reached` | the medium supplies the compound, or a reaction that runs makes it | | `biology.Reaction.runs` | every substrate of the reaction is reached | | `biology.Family.unexplained` | how many members of the family the mode cannot explain, each counted once however many rules or values find them so | | `biology.Person.inbred` | the person's parents are related by descent | | `biology.Person.carrier` | an unaffected person the mode requires to carry the allele | | `biology.Person.unexplained` | the mode cannot explain the person's record | **Two relations** are concluded between people. `biology.Parent.c.p` holds for each parent recorded of a child `c`. `biology.Ancestor.x.y` holds for each parent, each parent's parent, and so on to the first generation recorded. **A scope is concluded from the medium outward.** A reaction that runs makes its products reached, and those start the next reactions. A cycle of reactions that make each other's substrates is reached only while something outside it supplies one of them; when the medium stops supplying that compound, the cycle is withdrawn with it. **Descent.** A child is **inbred** when its mother and father have an ancestor in common, or when one parent is the other's ancestor: cousins, siblings, an uncle and a niece, a father and a daughter. **Carriers.** In a recessive family, the unaffected parent of an affected child carries the allele, and so does the unaffected child of an affected parent. In an X-linked recessive family, the unaffected mother of an affected son carries it, and so does the unaffected daughter of an affected father. **What a mode cannot explain.** An affected child of two unaffected parents in a dominant family; an unaffected child of two affected parents in a recessive one; in an X-linked recessive family, an affected daughter whose father is unaffected, and an unaffected son whose mother is affected. ### 11.3 Queries | Call | Answers | |------|---------| | `biology.scope()` | every compound the network can make from its medium, sorted | | `biology.blocked()` | every reaction that cannot run, sorted | | `biology.needs(target, candidates)` | each candidate that, added to the medium alone, would let the network make `target`, in the order given; nothing is written | | `biology.people(name)` | every person recorded in a family, sorted | | `biology.ancestors(name)` | every recorded ancestor of a person, sorted | | `biology.inbred()` | every child whose parents are related by descent, sorted | | `biology.carriers(name)` | every member of a family the mode requires to carry the allele, sorted | | `biology.unexplained(name)` | every member of a family whose record the mode cannot explain, sorted | | `biology.unsettled()` | every compound, reaction, family and person with a field you write that sources give two different values, as `person nell affected`, sorted | | `biology.complement(dna)` | the other strand, read in its own direction | | `biology.transcribe(dna)` | the messenger RNA of a coding strand: `U` for each `T` | | `biology.translate(rna, table)` | the protein from the first `AUG` to the first stop, with `table` 1, the standard code, or 2, the vertebrate mitochondrial code; `""` with no `AUG` | | `biology.gc(dna)` | the share of bases that are `G` or `C` | | `biology.gametes(genotype)` | the gametes of a genotype written two letters a gene, `"AaBb"` | | `biology.zygote(a, b)` | the genotype two gametes make | | `biology.cross(a, b)` | how many pairings of the parents' gametes give each genotype | | `biology.phenotypes(a, b)` | the same pairings by what they show | | `biology.chi_square(observed, ratio)` | Pearson's statistic for counts against an expected ratio | | `biology.hardy_weinberg(aa, ab, bb)` | the first allele's frequency, and the chi-square of the counts against `p^2 : 2pq : q^2` | | `biology.logistic(r, k, n0, t, dt)` | a population grown toward capacity `k` by fourth-order Runge–Kutta | | `biology.logistic_exact(r, k, n0, t)` | the same population from the equation's solution | | `biology.predator_prey(alpha, beta, gamma, delta, x0, y0, t, dt)` | prey, predators, and the relative change in the quantity the exact motion keeps | | `biology.drift(n, p0, generations, seed)` | an allele's frequency after random sampling each generation, and the generation it was fixed or lost in, -1.0 if neither | | `biology.fixation(n, p0, runs, generations, seed)` | the share of runs in which the allele is fixed | | `biology.ring(start, steps)` | the states of a ring of genes that repress one another | | `biology.period(start)` | how many steps the ring takes to return to a state it has been in | ```python import biology biology.family("marsh", "autosomal recessive") biology.person("ada", "marsh", "F", False, "", "") biology.person("ben", "marsh", "M", False, "", "") biology.person("cass", "marsh", "F", True, "ada", "ben") biology.reaction("hexokinase", "glucose", "", "g6p", "") print(biology.carriers("marsh")) print(biology.needs("g6p", ["xylose", "glucose"])) print(biology.period("1000")) ``` ### 11.4 What the answers can and cannot mean **A scope is reachability, not growth.** A compound is reached when a chain of reactions leads to it from the medium. The module does not check that a reaction is balanced, runs in the direction written, has its enzyme or is fast enough, and a reaction written as two directions is two reactions. **`needs()` asks, and writes nothing.** For each candidate it supposes that compound is in the medium alone, asks whether the target is then reached, and discards the supposition. The medium, the scope and every other fact are as they were afterwards. A target already reached is answered by every candidate, and a candidate that is the target itself is answered. **Carriers and unexplained members assume a model.** Every pedigree conclusion assumes that the trait shows in everyone who has its genotype, and that no new mutation arose in the family. A person marked unexplained is where one of those, or the record, does not hold: incomplete penetrance, a new mutation, a misattributed parent or a wrong mode all produce one. The module names only the carriers a mode requires from the generation next to them. It does not estimate the chance that anyone else carries, and a person it does not name may carry the allele. **A correction replaces; a disagreement is kept.** The writers write as `pedigree`, `network` and `medium`, so writing a person or a reaction again replaces what the same source wrote and withdraws every conclusion it no longer supports. A fact written under another source is held beside the first. Every conclusion it reaches is drawn from both values, so a person recorded affected by one source and unaffected by another is reasoned about as both, and `biology.unsettled()` names the person and the field. A second source that writes the same value agrees with the first, and is not named. No query refuses because sources disagree. A family counts each member the mode cannot explain once: a daughter two modes leave unexplained, while two sources give the family's mode, is one member, not two. A person whose family two sources disagree on is counted in each family a value names. **The calculations are the formulas as written.** The Runge–Kutta scripts take fixed steps and are as accurate as the step is small. `drift` and `fixation` draw from a fixed generator, so a seed gives the same run every time: a run is a sample, and a single one says little about the chance of fixation. `chi_square` refuses a count or a share below 0, and `logistic` and `logistic_exact` a capacity not above 0 or a population below 0, with `ValueError`. A class the ratio expects none of adds nothing to a chi-square where none were observed in it, so `hardy_weinberg` answers a sample of one homozygote, and makes it `inf` where some were. `predator_prey` refuses populations that pass the largest real. Where a step of a value script's work would pass the largest real or fall below the smallest normal one while its value would not, the script works it so that it stays in range, which changes no digit of an answer whose work stays in range. ## 12. `astronomy` — sites, stars and bookings, and the arithmetic of the sky `astronomy` reads two kinds of record you write as facts. From sites, each with a latitude, a longitude and the faintest magnitude its instrument reaches, and stars, each with a right ascension, a declination and a magnitude, it concludes for every site the stars that never set, the stars that never rise and the stars it can see. From nights, the dark time at a site, and bookings, an instrument booked at a site for a star, it concludes that a booking is observable for exactly the stretch where the booking, a night and the star's time above the horizon overlap, and reports that stretch. Its value scripts calculate what observing calculates: Julian dates, Greenwich and local sidereal time, a star's altitude and its rising and setting, the Sun's position and the length of the day, Kepler's equation, and the two-body problem. **Version 1.0.2.** Download it at [dke.langsyn.com/source/astronomy/latest/astronomy.dpy](https://dke.langsyn.com/source/astronomy/latest/astronomy.dpy), which always serves the current release, or name the version itself at `https://dke.langsyn.com/source/astronomy/1.0.2/astronomy.dpy`. **A sky is facts about names.** A star never sets at a site when it is so far from the pole the site is nearest that it stays above the horizon all day, and never rises when it is so far from it that it stays below. Those limits depend on the site's latitude alone, and the module works them out for you. Angles are in degrees, longitude is positive east and latitude and declination positive north, a smaller magnitude is a brighter star, and every time is UT. ### 12.1 Kinds and the fields you write | Kind | Fields you write | |------|------------------| | `astronomy.Site` | `lat`, `lon`, `limit`; never the four declination limits of 12.2, which are computed from `lat` | | `astronomy.Star` | `ra`, `dec`, `mag` | | `astronomy.Night` | `site`, `opens`, `closes` | | `astronomy.Up` | `star`, `site`, `rises`, `sets` | | `astronomy.Booking` | `site`, `star`, `starts`, `ends`; never `observable` | | `astronomy.Circumpolar` | none: a relation the rules conclude between a star and a site | | `astronomy.Hidden` | none: a relation the rules conclude between a star and a site | | `astronomy.Visible` | none: a relation the rules conclude between a star and a site | A **site**'s `lat` and `lon` are its latitude and longitude in degrees, and its `limit` the faintest magnitude its instrument reaches. A **star**'s `ra` and `dec` are its right ascension and declination in degrees, and its `mag` its magnitude. A **night** names its `site` and is the dark time there, and a **booking** names the `site` an instrument is booked at and the `star` it is booked for. An **up** is a star above a site's horizon, from its rising to its setting. A night's `site`, a booking's `site` and `star`, and an up's `star` and `site` are written with a **window**, the stretch of time they hold for. So that a window can be read when it is not now, it is also recorded as two facts that hold at all times: `opens` and `closes` for a night, `starts` and `ends` for a booking, `rises` and `sets` for an up. Five scripts write these facts for you: - `astronomy.site(name, latitude, longitude, faintest)` writes a site under the source `sites`. - `astronomy.star(name, right_ascension, declination, magnitude)` writes a star under the source `catalogue`. - `astronomy.night(name, where, start, end)` writes a night at the site `where` from `start` to `end`, under the source `nights`. - `astronomy.book(name, where, target, start, end)` books the star `target` at the site `where` from `start` to `end`, under the source `bookings`. - `astronomy.up(target, where, start)` writes the stretch the star `target` is above the horizon of the site `where`, under the source `ephemeris`, named `__` by the date of `start`. It is the first passage that has not ended by `start`: pass a night's start to get that night's passage, which may have begun before it. Writing a name again corrects it. `up()` writes nothing for a star that never rises, and for one that never sets it writes the day from `start`. ### 12.2 Derived and maintained for you | Field | Meaning | |-------|---------| | `astronomy.Site.circumpolar_above` | `90 - lat`: at a northern site, the declination above which a star never sets | | `astronomy.Site.hidden_below` | `lat - 90`: at a northern site, the declination below which a star never rises | | `astronomy.Site.circumpolar_below` | `-90 - lat`: at a southern site, the declination below which a star never sets | | `astronomy.Site.hidden_above` | `90 + lat`: at a southern site, the declination above which a star never rises | | `astronomy.Booking.observable` | the booking, a night at its site and its star's time above that site's horizon overlap, and it holds for that overlap alone | **Three relations** are concluded between a star and a site, `Circumpolar.s.t` for a star `s` and a site `t`. `astronomy.Circumpolar` holds for a star that never sets at the site, `astronomy.Hidden` for one that never rises, and `astronomy.Visible` for one that rises, or never sets, and is no fainter than the site's `limit`. A site at latitude 0 is read as a northern site. **Observing.** A booking is observable only where all three of its premises hold: the booking itself, a night at its site, and an up of its star at its site. The conclusion holds for the stretch where the three overlap, so reading it at a moment inside the booking answers whether the star is up and the night dark then. A star that never rises has no up, and its booking is never observable. ### 12.3 Queries | Call | Answers | |------|---------| | `astronomy.circumpolar(where)` | the stars that never set at a site, sorted | | `astronomy.hidden(where)` | the stars that never rise at a site, sorted | | `astronomy.visible(where)` | the stars that rise at a site, or never set, and are no fainter than its limit, sorted | | `astronomy.stars_in(relation, where)` | the stars in `"circumpolar"`, `"hidden"` or `"visible"` for a site, sorted; any other name reads `"visible"` | | `astronomy.observable_at(t)` | the bookings observable at an instant, sorted | | `astronomy.window(booking)` | the first and last instants a booking is observable for, as text, or `[]` where it never is | | `astronomy.unsettled()` | every site and star with a field you write that sources give two different values, as `star vega dec`, sorted | | `astronomy.number(c)` | the number a claim holds, an integer as a real, and 0.0 for any other value | | `astronomy.instant_of(c)` | the instant a claim holds, as a list of one, and `[]` for any other value | | `astronomy.jd(year, month, day)` | the Julian date of a calendar date, the day carrying its fraction | | `astronomy.jd_of(t)` | the Julian date of an instant | | `astronomy.instant(j)` | the instant of a Julian date, to the nanosecond | | `astronomy.gmst(j)` | Greenwich mean sidereal time in degrees, from 0 to 360 | | `astronomy.lst(j, longitude)` | local mean sidereal time at a longitude, east positive | | `astronomy.hms(degrees)` | an angle of the sky's rotation as hours, minutes and seconds, 15 degrees an hour | | `astronomy.altitude(declination, latitude, hour_angle)` | a body's altitude above the horizon, in degrees | | `astronomy.semi_arc(declination, latitude, horizon)` | the hour angle at which a body crosses the altitude `horizon`: 0 for one that never reaches it, 180 for one that never falls below it | | `astronomy.rise_set(ra, declination, latitude, longitude, day)` | a star's rising, transit and setting around its transit on the day starting at `day`, as a list of instants | | `astronomy.sun(j)` | the Sun's right ascension and declination at a Julian date | | `astronomy.day_length(latitude, j)` | the hours from sunrise to sunset at a latitude, 24 where the Sun never sets and 0 where it never rises | | `astronomy.kepler(mean_anomaly, e)` | the eccentric anomaly that solves Kepler's equation, and the steps it took | | `astronomy.true_anomaly(eccentric_anomaly, e)` | the angle from perihelion seen from the focus | | `astronomy.two_body(e, orbits, per_orbit)` | the energy at the start and the end of an orbit integrated by fourth-order Runge-Kutta, and the distance from where Kepler's equation puts the body | ```python import astronomy astronomy.site("oslo", 59.91, 10.75, 6.0) astronomy.star("polaris", 37.95, 89.26, 1.98) astronomy.star("canopus", 95.99, -52.70, -0.74) astronomy.star("vega", 279.23, 38.78, 0.03) print(astronomy.circumpolar("oslo")) print(astronomy.hidden("oslo")) print(astronomy.day_length(59.91, astronomy.jd(2026, 6, 21.0))) ``` ### 12.4 What the answers can and cannot mean **The sky is geometry.** Circumpolar, hidden and visible are limits of declination set by latitude. They take the horizon to be the geometric one, a level plane through the observer: they allow for no refraction, terrain, twilight, Moon or cloud, and `visible` compares a star's magnitude with the site's `limit` and nothing else. Rising and setting from `up()` and `rise_set()` are the same: for the geometric horizon unless an altitude is given to `semi_arc()`, and from the sidereal rate. Refraction lifts a body about half a degree, which `day_length()` includes for the Sun. A star whose declination is exactly on a limit is neither circumpolar nor hidden. **Coordinates are as you supplied them.** The module applies no precession, nutation, aberration or proper motion to a star, so its right ascensions and declinations are taken to be for the epoch you want the answer in. Sidereal time is the mean one. A star's rising and setting are computed for a declination that does not change over the passage. **An observable booking is an overlap, not a promise.** `observable` says that the booking, a night at the site and the star's time above the horizon overlap. It does not read the star's magnitude, the site's `limit`, the Moon or the weather. `window()` reports the stretch from the last of the three opening to the first of them closing, and is `[]` for a booking that never overlaps; it expects the booking to have been written. An `up()` is for one passage, so a night long enough to hold two of a star's passages is covered for the first alone, and two calls on the same date for one star and site replace one another. **The sky is written by you.** A rising and a setting are facts you write with `up()`; nothing concludes one from the star and the site alone, and an up already written does not follow a correction to a star's or a site's coordinates until you write it again. **A correction replaces; a disagreement is kept.** The writers write as `sites`, `catalogue`, `nights`, `bookings` and `ephemeris`, so writing a site or a star again replaces what the same source wrote and withdraws every conclusion it no longer supports. A fact written under another source is held beside the first, and every conclusion is drawn from both values: a star recorded at two declinations can be `Hidden` by one and `Visible` by the other, and `astronomy.unsettled()` names the star or the site and the field. No read refuses because sources disagree, except `up()` and `window()`. `up()` needs one value of the star's right ascension and declination and of the site's latitude and longitude, and is refused while any has two. `window()` needs one value of the booking's start and of its end, and is refused while either has two; `unsettled()` does not name a booking. `observable_at()` is not refused: a booking is observable at an instant if any value of it is. **The calculations are the formulas as written.** `jd()` follows Meeus: the Julian calendar before 1582 October 15 and the Gregorian from then, and a date from the ten that calendar dropped is read as Gregorian. A Julian date held as a real steps in about 40 microseconds, so `instant()` returns the instant of the real rather than of the decimal you wrote. `sun()` is good to about 0.01 degrees from 1950 to 2050. `kepler()` stops when a step changes the answer by less than 1e-15, in at most 64 steps, for an eccentricity from 0 up to 1. `two_body()` takes fixed steps and is as accurate as the step is small: the energy of every orbit it integrates is -0.5, so the change in the energy is the integration's error. `kepler()` refuses an eccentricity outside 0 to 1, and takes a mean anomaly past π to within π of 0 first, so it finds `E` for any `M`, where 1.0.1 stopped short below -π or a few turns out. `gmst()` refuses a date so far from 2000 that its angle passes the largest real. An angle is taken modulo 360 degrees by `math.fmod` first, which is exact, so an angle past a turn loses no digits on its way to radians: past a turn the sine of one is now within a unit or so in the last place, where it was off by a median of 1,251. Where a step of a value script's work would pass the largest real or fall below the smallest normal one while its value would not, the script works it so that it stays in range, which changes no digit of an answer whose work stays in range. ## 13. `geography` — regions, maps, places and the arithmetic of the Earth `geography` reads three kinds of record you write as facts. From regions, each with the region it lies in directly and a level, it concludes every region a region lies within, to any depth, and how many regions each one holds. From a map, the borders between regions and the colour each region is given, it concludes which regions neighbour which and which neighbours share a colour, and it can choose colours so that none do. From places by latitude and longitude and boxes of latitude and longitude, it concludes which places lie in which boxes, a box across the antimeridian included. Its value scripts calculate what geography calculates: the distance, bearing and destination along a great circle on a sphere, the distance and azimuths between two points on the WGS84 ellipsoid and the point a distance away, the Mercator projection and its scale, and the area of a triangle on the sphere. **Version 1.0.2.** Download it at [dke.langsyn.com/source/geography/latest/geography.dpy](https://dke.langsyn.com/source/geography/latest/geography.dpy), which always serves the current release, or name the version itself at `https://dke.langsyn.com/source/geography/1.0.2/geography.dpy`. **A hierarchy is facts about names.** A region names the region it lies in directly, its parent, and a level, any word you use for what kind of region it is. A region **lies within** its parent, its parent's parent, and so on to the outermost region recorded. A **border** joins two regions, and two regions joined by a border are **neighbours**, both ways round. A **clash** is two neighbours the map gives one colour. A **box** is a range of latitude and a range of longitude, in degrees; a box whose western edge lies east of its eastern one crosses the antimeridian, 180°, and holds the longitudes from the western edge round to the eastern. Angles are in degrees and distances in metres. ### 13.1 Kinds and the fields you write | Kind | Fields you write | |------|------------------| | `geography.Region` | `parent`, `level`, `colour`; never `holds`, which the rules conclude | | `geography.Within` | none: a relation the rules conclude between a region and a region it lies within | | `geography.Border` | `a`, `b` | | `geography.Neighbour` | none: a relation the rules conclude between two regions that share a border | | `geography.Clash` | none: a relation the rules conclude between two neighbours of one colour | | `geography.Place` | `lat`, `lon` | | `geography.Box` | `south`, `north`, `west`, `east` | | `geography.Inside` | none: a relation the rules conclude between a place and a box it lies in | A **region**'s `parent` is the name of the region it lies in directly, left unwritten for one that lies in none you record; its `level` is what kind of region it is, in any word you use; and its `colour` is the colour a map gives it. A **border**'s `a` and `b` are the names of the two regions it lies between. A **place** is at latitude `lat` and longitude `lon`, north and east positive. A **box** runs from `south` to `north` and from `west` to `east`. Five scripts write these facts for you: - `geography.region(name, parent, level)` writes a region under the source `regions`. Pass `""` for a parent not recorded. - `geography.border(a, b)` writes a border between two regions, and `geography.colour(name, c)` the colour a region has, under the source `map`. - `geography.place(name, lat, lon)` and `geography.box(name, south, north, west, east)` write a place and a box under the source `places`. A sixth, `geography.paint(palette)`, writes colours under `map` for you: it gives each region named in a border the colour that `geography.dsatur()` chooses, number k as `palette[k]`. Writing a name again corrects it. Passing `""` writes nothing, so it does not withdraw a parent an earlier call recorded: write the parent you mean instead. Record every region, the outermost too, with `geography.region`: a region is a name that call has recorded, and a parent nobody recorded concludes nothing. ### 13.2 Derived and maintained for you | Field | Meaning | |-------|---------| | `geography.Region.holds` | how many regions lie within the region, at any depth, each counted once however many paths lead to it; unwritten for a region that holds none | **Four relations** are concluded. `geography.Within.r.p` holds for each region `r` and each region `p` it lies within: its parent, its parent's parent, and so on. `geography.Neighbour.x.y` holds both ways round for each border. `geography.Clash.x.y` holds both ways round for two neighbours the map gives one colour; it is declared so that a map with no clash reads as empty rather than as an unknown kind. `geography.Inside.p.b` holds for each place `p` within a box `b`, the edges included. **A move leaves every region above.** Write a region again with another parent and it lies within the new parent's regions and no longer within the old parent's, and every `holds` follows. The conclusions rest on the facts, and withdraw when a fact is corrected. **A box across the antimeridian is read as two ranges.** A place is within when its latitude is from the box's `south` to its `north` and its longitude is at or east of the box's `west`, or at or west of its `east`. Longitudes are compared as written, so write them from -180 to 180. A box whose two longitudes are equal is that one meridian, and a box whose `south` lies north of its `north` holds no place. ### 13.3 Queries | Call | Answers | |------|---------| | `geography.regions()` | every region recorded with a level, sorted | | `geography.within(name)` | every region a region lies within, sorted | | `geography.regions_in(name, at)` | every region within a region at the level `at`, or at any level for `""`, sorted | | `geography.neighbours(name)` | every recorded region that shares a border with a region, sorted | | `geography.clashes()` | every pair of neighbours the map gives one colour, as `"x y"` with `x` first, sorted | | `geography.places_in(name)` | every place within a box, sorted | | `geography.unsettled()` | every region, border, place and box with a field you write that sources give two different values, as `place oslo lat`, sorted | | `geography.dsatur()` | a colour number from 0 for every region named in a border, so that no two neighbours share one; writes nothing | | `geography.mean_radius()` | the Earth's mean radius, 6371008.8 metres | | `geography.central_angle(lat1, lon1, lat2, lon2)` | the angle between two points seen from the sphere's centre, in radians | | `geography.haversine(lat1, lon1, lat2, lon2)` | the great-circle distance on the sphere, in metres | | `geography.bearing(lat1, lon1, lat2, lon2)` | the initial bearing of the great circle from the first point to the second, from 0 to 360 | | `geography.destination(lat, lon, heading, distance)` | the point reached on a bearing after a distance in metres on the sphere: `[lat, lon]` | | `geography.spherical_excess(lat1, lon1, lat2, lon2, lat3, lon3)` | the amount by which a spherical triangle's angles sum past π, in radians | | `geography.triangle_area(lat1, lon1, lat2, lon2, lat3, lon3)` | the area of that triangle on the sphere, in square metres | | `geography.mercator(lat)` | the Mercator northing for a latitude, in units of the sphere's radius | | `geography.mercator_inverse(y)` | the latitude a Mercator northing comes from | | `geography.mercator_scale(lat)` | how much the projection enlarges a short distance at a latitude, `1 / cos` of the latitude | | `geography.dms(x)` | an angle as `[degrees, minutes, seconds]`, each carrying the angle's sign | | `geography.vincenty_inverse(lat1, lon1, lat2, lon2)` | on the WGS84 ellipsoid, `[metres, azimuth there, azimuth back, rounds]`; `[0.0, 0.0, 0.0, -1.0]` when near antipodal points do not converge | | `geography.vincenty_direct(lat1, lon1, az1, s)` | on the WGS84 ellipsoid, the point a distance `s` along the geodesic from a point at azimuth `az1`: `[lat, lon, azimuth back]` | ```python import geography geography.region("norway", "", "country") geography.region("vestland", "norway", "county") geography.region("bergen", "vestland", "municipality") geography.place("bergen_centre", 60.39, 5.32) geography.box("vestland_box", 59.0, 62.0, 4.5, 8.0) print(geography.within("bergen")) print(geography.regions_in("norway", "municipality")) print(geography.places_in("vestland_box")) print(geography.haversine(59.91, 10.75, 60.39, 5.32)) ``` ### 13.4 What the answers can and cannot mean **A hierarchy is what you recorded.** `within` and `regions_in` follow the parents you wrote. The module does not know that a county is larger than a municipality, that two regions overlap or that a region has a border it was not given; a level is a word, and it is compared as one. **A neighbour is a border you wrote.** Two regions are neighbours only when a border names them, and the module does not decide from places or boxes that two regions touch. The readers list only regions recorded with a level, so record a region before you read its neighbours or its clashes. `dsatur` reads the borders alone: it names every region a border names, recorded or not. **A colouring is not the fewest colours.** `dsatur` colours the region whose neighbours already show the most colours, the most neighbours breaking a tie and then the name, and gives it the lowest number none of them shows. No two neighbours share one, but the heuristic does not promise the fewest colours any map needs. `paint` needs a palette with a colour for every number it chooses; a shorter palette is refused part way, after the colours it reached have been written, so give it a palette as long as the colouring needs, which `dsatur` shows. `dsatur` ignores a border from a region to itself. **A place is a point, a box is a rectangle in latitude and longitude.** A box is not an area on the ground: its eastern and western edges converge toward a pole, and a box that crosses the antimeridian is read as the two ranges it covers. A place on an edge is within. **The sphere is the mean sphere.** `haversine`, `bearing`, `destination`, `triangle_area` and `central_angle` treat the Earth as a sphere of radius 6371008.8 metres, so a distance can differ from the ellipsoid's by a fraction of a percent. `vincenty_inverse` and `vincenty_direct` use the WGS84 ellipsoid and are good to well under a millimetre where they converge. They return full-precision numbers and round nothing. Every coordinate and azimuth is taken modulo 360 degrees by `math.fmod` first, which is exact, so one past a turn loses no digits on its way to radians. Where a step of a value script's work would pass the largest real or fall below the smallest normal one while its value would not, the script works it so that it stays in range, which changes no digit of an answer whose work stays in range. **Some questions have no answer.** `vincenty_inverse` stops after 200 rounds on points that are nearly antipodal, and answers `[0.0, 0.0, 0.0, -1.0]` rather than a distance; the fourth number is how many rounds it took, and -1.0 means it did not converge. Two points that coincide answer `[0.0, 0.0, 0.0, 1.0]`. `spherical_excess` answers 0.0 for three points on one great circle. The Mercator projection has no northing at a pole: do not ask for 90 or -90, where the answer is either a very large number or a refusal, and expect the scale to grow without bound as the latitude nears either pole. **A correction replaces; a disagreement is kept.** The writers write as `regions`, `map` and `places`, so writing a region or a place again replaces what the same source wrote and withdraws every conclusion it no longer supports. A fact written under another source is held beside the first. Every conclusion it reaches is drawn from both values, so a region two sources put in two parents lies within both, and a place two sources put at two latitudes is within every box either latitude falls in. `geography.unsettled()` names the region or the place and the field. A second source that writes the same value agrees with the first, and is not named. No query refuses because sources disagree. A region holds each region within it once: a city two sources put both in its country and directly in its continent lies within the continent by two paths, and the continent holds it once. ## 14. `mathematics` — numbers, finite operations and triangles, and the arithmetic of number theory `mathematics` reads three kinds of record you write as facts. From whole numbers, each with a row for every candidate divisor up to half of it, it concludes which candidates divide, the sum of each number's proper divisors, and whether the number is prime or composite and perfect, abundant or deficient. From a binary operation on a finite set, given as its table, it concludes the identity, whether the operation is closed, commutative and associative, which elements have a two-sided inverse, and whether it is a group and an abelian one; where a property fails, it names the cells that break it. From a triangle's three sides in whole units it concludes whether they close up into a triangle at all, and if they do, whether it is acute, right or obtuse and equilateral, isosceles or scalene. Its value scripts calculate number theory and numerical methods: Euclid's algorithm and its extension, modular inverses and powers, a primality test, factorization, Euler's totient, the sieve, binomial coefficients, factorials and Fibonacci numbers, Collatz trajectories, continued fractions and Pell's equation, roots by Newton's method, integrals from samples by the trapezoidal rule and Simpson's, and determinants and linear systems by elimination. **Version 1.0.2.** Download it at [dke.langsyn.com/source/mathematics/latest/mathematics.dpy](https://dke.langsyn.com/source/mathematics/latest/mathematics.dpy), which always serves the current release, or name the version itself at `https://dke.langsyn.com/source/mathematics/1.0.2/mathematics.dpy`. **A number is judged by its candidates.** A number is written with a candidate for each `k` from 1 to half of it, and a candidate **divides** when what is left over is 0. A number is **prime** when it is above 1 and no candidate above 1 divides it, **composite** when one does, and **perfect**, **abundant** or **deficient** as the sum of its proper divisors equals, exceeds or falls short of it. **An operation is its table.** The product `a · b` of every pair of elements is a cell, and associativity is asked of every three elements in order. **A triangle is three whole lengths.** ### 14.1 Kinds and the fields you write | Kind | Fields you write | |------|------------------| | `mathematics.Number` | `n`; never `divisor_sum`, `prime`, `composite`, `perfect`, `abundant`, `deficient`, `divisor_count`, `claims` or `disputed`, which the rules conclude | | `mathematics.Candidate` | `of`, `n`, `k`; never `divides`, which a rule concludes, or its remainder, which is computed | | `mathematics.Operation` | `order`; never `identity`, `closed`, `commutative`, `associative`, `group` or `abelian` | | `mathematics.Element` | `op`, `el`; never `identity`, `invertible` or `noninvertible` | | `mathematics.Cell` | `op`, `a`, `b`, `c`, `left`, `right`; never `outside`, `noncommuting`, `to_identity` or `twosided` | | `mathematics.Triple` | `op`, `x`, `z`, `xy`, `yz`; never `left`, `right` or `breaks` | | `mathematics.Triangle` | `a`, `b`, `c`; never `acute`, `right`, `obtuse`, `equilateral`, `isosceles` or `scalene`; its perimeter, gap, slack, heron and valid are computed | A **number** is named `n` followed by its value, and its `n` is the value, at least 1. A **candidate** names the number it is `of`, that number's `n` and its own `k`. An **operation** has an `order`, how many elements the set has. An **element** names its `op` and its label `el`, and is itself named `_