Add complex as a first-class scalar type in the nml-tools schema language and
support it consistently across schema validation, namelist validation,
generated Fortran, templates, documentation, and f2py/Python wrappers.
properties:
impedance:
type: complex
x-fortran-kind: dp
default: [1.0, -2.0]
This is an intentional extension of the JSON Schema type vocabulary. An
nml-tools schema remains a JSON- or YAML-serializable document and continues to
use JSON Schema concepts, but it is an nml-tools dialect rather than a strictly
conforming Draft 2020-12 schema.
The dialect and meta-schema publication work is tracked separately in #57.
Standard namelist parsing, including complex literals and complex-part designators, is described in #55.
Motivation
Fortran programmers expect complex to be an intrinsic scalar type:
complex(kind=dp) :: impedance
&solver
impedance = (1.0, -2.0)
/
Representing this as a string, derived object, or specially marked JSON array
would preserve strict JSON Schema types at the cost of making the schema less
clear. nml-tools has evolved into a standalone Fortran schema and generation
tool with its own configuration, validation policy, operational defaults, and
x-fortran-* extensions. Its authoring model should therefore prioritize
Fortran semantics where JSON Schema has no natural equivalent.
The existing JSON Schema names should remain where they map cleanly:
number maps to Fortran real values;
integer maps to Fortran integer values;
boolean maps to Fortran logical values; and
string maps to Fortran character values.
Complex is different because JSON Schema has no scalar type that represents
it. Adding type: complex is a narrow, explicit divergence rather than a
reason to rename the existing compatible types.
Schema Syntax
Scalar Complex Values
properties:
coefficient:
title: Complex coefficient
description: Coefficient in Cartesian form.
type: complex
x-fortran-kind: dp
default: [1.0, 0.5]
examples:
- [0.0, 1.0]
- [-2.5, 0.0]
default and each examples entry use a canonical two-element sequence:
[real-part, imaginary-part]
Both entries must be JSON numbers and must not be booleans. The sequence is a
schema-literal representation of one complex scalar; it does not make the
Fortran field an array.
The normalized Python value should be the built-in complex type. Raw parser
tokens may remain separate until schema evaluation so diagnostics, decimal
mode, and source precision are preserved.
Complex Arrays
Complex arrays use the existing single-level array schema:
properties:
coefficients:
type: array
x-fortran-shape: n_coefficients
items:
type: complex
x-fortran-kind: dp
default:
- [1.0, 0.0]
- [0.0, 1.0]
The outer sequences represent Fortran array dimensions according to the
existing nml-tools array-default rules. Each innermost two-element sequence is
one complex scalar.
Supported Keywords
A complex scalar should support:
type: complex;
x-fortran-kind;
default;
examples;
title, description, and $comment; and
- parent-object
required membership under the same effective-default policy
as other scalar types.
Reject these combinations initially:
x-fortran-len;
x-fortran-shape directly on a complex scalar;
properties, items, or x-fortran-type on a complex scalar;
minimum, maximum, exclusiveMinimum, or exclusiveMaximum, because
complex values are unordered; and
enum, until exact equality, NaN, signed-zero, and generated Fortran
comparison behavior are specified.
x-fortran-shape remains valid on an enclosing type: array whose items are
complex.
Namelist Behavior
Whole complex values use standard namelist syntax:
&solver
coefficient = (1.0, -2.0)
coefficients = (1.0, 0.0), (0.0, 1.0)
/
In POINT decimal mode, a comma separates the real and imaginary parts. In
COMMA decimal mode, a semicolon separates them. The complete parenthesized pair
is one effective item; a namelist null omits the complete complex value rather
than one component.
The schema-aware parser should preserve a complex literal as two source tokens
and convert it only after resolving a complex target. Complex-part designators
%re and %im belong to the parser/evaluator work and should map to the
corresponding part of the stored scalar.
The current f90nml path already returns Python built-in complex values for
whole complex literals. Complex support may therefore begin before the custom
parser migration, but complete standard designator and decimal-mode behavior
depends on that migration.
Validation And Presence
Validate a Python built-in complex as one scalar value. Schema defaults and
examples should be normalized from [real, imag] before applying generated or
runtime behavior.
Generated storage needs three distinguishable states for a complex value
without a default:
absent: both parts contain the reserved NaN sentinel
complete: neither part contains the reserved NaN sentinel
partial: exactly one part contains the reserved NaN sentinel
- An absent optional value is valid and is not set.
- An absent required value is invalid.
- A partial value is invalid, including an optional value touched only through
%re or %im.
- A complete value is set and receives normal validation.
This likely requires initializing both parts with quiet NaN values and checking
the real and imaginary parts separately with ieee_is_nan. Concrete complex
values containing NaN cannot be represented independently from this sentinel
policy and should be rejected initially. Infinity policy should match the
eventual scalar real-value policy.
A configured default initializes both parts and satisfies presence according
to the general operational-default policy.
Generated Fortran
Generate scalar declarations such as:
complex(kind=dp) :: coefficient
Generate defaults using explicit component literals and kind-correct
construction, for example:
this%coefficient = cmplx(1.0_dp, -2.0_dp, kind=dp)
Required generator changes include:
- classify
complex as an intrinsic scalar category before derived-object and
array handling;
- include complex kinds in imports, declarations, defaults, sentinels,
comparisons, and generated validation;
- add complex scalar and complex-array arguments to
init, set, is_set,
and is_valid behavior;
- add complex array allocation, assignment, reshape, runtime-dimension, and
default handling where the corresponding intrinsic-array feature exists;
- generate standard complex literals in namelist templates; and
- include complex values in any generated namelist writer once that feature is
implemented.
Flexible-tail complex arrays may remain unsupported initially if their
presence and filled-shape behavior cannot reuse the real-array implementation
without ambiguity. Any restriction must be targeted and documented.
Kind Configuration
In Fortran, the kind parameter of a complex value is the kind of its real and
imaginary components. A project may therefore use the same names for real and
complex kinds:
integer, parameter :: sp = real32
integer, parameter :: dp = real64
Other projects expose distinct aliases even when their values are equal:
integer, parameter :: sp = real32
integer, parameter :: dp = real64
integer, parameter :: spc = real32
integer, parameter :: dpc = real64
Extend [kinds] with an optional complex allowlist while keeping simple
configurations concise:
[kinds]
module = "mo_kind"
integer = ["i4", "i8"]
real = ["sp", "dp"]
complex = ["spc", "dpc"]
Apply this policy:
- when
kinds.complex is present, use it as the allowlist for complex schema
properties;
- when
kinds.complex is omitted, fall back to kinds.real;
- keep
kinds.map shared across categories, because kind aliases remain
globally resolved Fortran identifiers; and
- require no kind configuration for an unkinded
type: complex property.
The fallback permits both real(kind=dp) and complex(kind=dp) without
duplicating configuration. The explicit list permits project conventions such
as real(kind=dp) and complex(kind=dpc). The two aliases may have the same
integer value while retaining distinct names in generated source.
f2py And Python Interfaces
Python-facing APIs should use Python and NumPy complex values rather than schema
literal pairs:
cfg.set(coefficient=1.0 - 2.0j)
For arrays, accept NumPy complex arrays through the existing array-shape
normalization path. f2py supports intrinsic Fortran complex scalars and arrays;
Python scalar arguments may use built-in complex, while arrays normally use
NumPy complex64 or complex128 dtypes.
Extend the f2py configuration with a separate complex C type table:
[kinds]
module = "iso_fortran_env"
real = ["real32", "real64"]
map = { sp = "real32", dp = "real64" }
[f2py.c_types.real]
sp = "float"
dp = "double"
[f2py.c_types.complex]
sp = "complex_float"
dp = "complex_double"
The [kinds].complex fallback concerns allowed Fortran kind names only. Do not
fall back from f2py.c_types.complex to f2py.c_types.real: double and
complex_double are different f2py C representations. Require an explicit
complex mapping for every named complex kind used by generated f2py wrappers.
No mapping is needed for an unkinded complex declaration.
Extend F2pyKindUsage, F2pyCTypeMap, and .f2py_f2cmap generation with a
complex category. The generated map should contain entries such as:
dict(
real=dict(dp="double"),
complex=dict(dp="complex_double"),
integer=dict(i4="int"),
)
f2py also recognizes complex_long_double, but quad-precision interoperability
is more compiler- and platform-sensitive. Treat complex_float and
complex_double as the primary mappings and only promise quad-precision
support after it passes the supported compiler matrix.
The two-element schema representation is only for JSON/YAML defaults and
examples. It should not leak into the normal Python wrapper API.
Documentation And Templates
- Show the field type as
complex, including its configured kind.
- Render scalar defaults and examples in readable Fortran form, such as
(1.0, -2.0), while documenting the schema pair form.
- Render complex array examples in Fortran element order.
- Explain that a complex value is one Fortran scalar even though its schema
literal contains two numbers.
- Update the README section that currently lists complex as unsupported because
JSON lacks a native type.
- Add the new type to the published nml-tools dialect reference and
meta-schema.
Tests
Add focused coverage for:
- valid and invalid scalar defaults and examples;
- missing, extra, boolean, string, null, NaN, and infinite pair components;
- schema rejection of bounds, length, shape, object, and array keywords on a
complex scalar;
$defs and $ref reuse of complex scalar and complex-array item schemas;
- scalar and array namelist validation from real files;
- POINT-mode complex literals, nulls, repetition, and overlong buffers;
- partial
%re or %im assignment once the schema-aware parser supports it;
- required, optional, defaulted, absent, complete, and partial presence states;
- generated declarations, defaults, setters, sentinels, and validation checks;
- fixed and runtime-sized complex arrays;
- explicit complex kind lists and fallback to real kind lists;
- generated templates and Markdown documentation;
- separate f2py complex kind-map discovery and Python scalar/NumPy-array
wrappers; and
- generated Fortran runtime tests across the supported compiler matrix.
Run the normal project checks and update generated fixtures and examples that
gain complex fields.
Acceptance Criteria
type: complex is accepted as a first-class nml-tools scalar type.
- Complex schema literals use exactly two numeric entries in Cartesian order.
- Whole complex values validate from standard namelist syntax.
- Internal validation and Python interfaces use built-in complex values.
- Generated Fortran uses native
complex(kind=...) storage.
- Requiredness, defaults, optional absence, and partial component assignment
have explicit, tested behavior.
- Complex arrays work for the supported fixed and runtime array models.
[kinds].complex supports dedicated aliases and falls back to [kinds].real
when omitted.
- f2py wrappers expose natural Python/NumPy complex values.
- Named complex kinds use explicit
[f2py.c_types.complex] mappings.
- The documentation identifies
complex as an intentional nml-tools dialect
extension rather than strict JSON Schema.
Out Of Scope
- Complex ordering constraints.
- Complex enums in the first implementation.
- Polar schema literals.
- Arbitrary-precision components beyond the precision already supported for
real schema literals and Python values.
- A namelist-to-JSON conversion API.
- User-defined formatted I/O for application-specific complex-like types.
Add
complexas a first-class scalar type in the nml-tools schema language andsupport it consistently across schema validation, namelist validation,
generated Fortran, templates, documentation, and f2py/Python wrappers.
This is an intentional extension of the JSON Schema
typevocabulary. Annml-tools schema remains a JSON- or YAML-serializable document and continues to
use JSON Schema concepts, but it is an nml-tools dialect rather than a strictly
conforming Draft 2020-12 schema.
The dialect and meta-schema publication work is tracked separately in #57.
Standard namelist parsing, including complex literals and complex-part designators, is described in #55.
Motivation
Fortran programmers expect
complexto be an intrinsic scalar type:Representing this as a string, derived object, or specially marked JSON array
would preserve strict JSON Schema types at the cost of making the schema less
clear. nml-tools has evolved into a standalone Fortran schema and generation
tool with its own configuration, validation policy, operational defaults, and
x-fortran-*extensions. Its authoring model should therefore prioritizeFortran semantics where JSON Schema has no natural equivalent.
The existing JSON Schema names should remain where they map cleanly:
numbermaps to Fortran real values;integermaps to Fortran integer values;booleanmaps to Fortran logical values; andstringmaps to Fortran character values.Complex is different because JSON Schema has no scalar type that represents
it. Adding
type: complexis a narrow, explicit divergence rather than areason to rename the existing compatible types.
Schema Syntax
Scalar Complex Values
defaultand eachexamplesentry use a canonical two-element sequence:Both entries must be JSON numbers and must not be booleans. The sequence is a
schema-literal representation of one complex scalar; it does not make the
Fortran field an array.
The normalized Python value should be the built-in
complextype. Raw parsertokens may remain separate until schema evaluation so diagnostics, decimal
mode, and source precision are preserved.
Complex Arrays
Complex arrays use the existing single-level array schema:
The outer sequences represent Fortran array dimensions according to the
existing nml-tools array-default rules. Each innermost two-element sequence is
one complex scalar.
Supported Keywords
A complex scalar should support:
type: complex;x-fortran-kind;default;examples;title,description, and$comment; andrequiredmembership under the same effective-default policyas other scalar types.
Reject these combinations initially:
x-fortran-len;x-fortran-shapedirectly on a complex scalar;properties,items, orx-fortran-typeon a complex scalar;minimum,maximum,exclusiveMinimum, orexclusiveMaximum, becausecomplex values are unordered; and
enum, until exact equality, NaN, signed-zero, and generated Fortrancomparison behavior are specified.
x-fortran-shaperemains valid on an enclosingtype: arraywhoseitemsarecomplex.
Namelist Behavior
Whole complex values use standard namelist syntax:
In POINT decimal mode, a comma separates the real and imaginary parts. In
COMMA decimal mode, a semicolon separates them. The complete parenthesized pair
is one effective item; a namelist null omits the complete complex value rather
than one component.
The schema-aware parser should preserve a complex literal as two source tokens
and convert it only after resolving a complex target. Complex-part designators
%reand%imbelong to the parser/evaluator work and should map to thecorresponding part of the stored scalar.
The current f90nml path already returns Python built-in
complexvalues forwhole complex literals. Complex support may therefore begin before the custom
parser migration, but complete standard designator and decimal-mode behavior
depends on that migration.
Validation And Presence
Validate a Python built-in
complexas one scalar value. Schema defaults andexamples should be normalized from
[real, imag]before applying generated orruntime behavior.
Generated storage needs three distinguishable states for a complex value
without a default:
%reor%im.This likely requires initializing both parts with quiet NaN values and checking
the real and imaginary parts separately with
ieee_is_nan. Concrete complexvalues containing NaN cannot be represented independently from this sentinel
policy and should be rejected initially. Infinity policy should match the
eventual scalar real-value policy.
A configured default initializes both parts and satisfies presence according
to the general operational-default policy.
Generated Fortran
Generate scalar declarations such as:
Generate defaults using explicit component literals and kind-correct
construction, for example:
Required generator changes include:
complexas an intrinsic scalar category before derived-object andarray handling;
comparisons, and generated validation;
init,set,is_set,and
is_validbehavior;default handling where the corresponding intrinsic-array feature exists;
implemented.
Flexible-tail complex arrays may remain unsupported initially if their
presence and filled-shape behavior cannot reuse the real-array implementation
without ambiguity. Any restriction must be targeted and documented.
Kind Configuration
In Fortran, the kind parameter of a complex value is the kind of its real and
imaginary components. A project may therefore use the same names for real and
complex kinds:
Other projects expose distinct aliases even when their values are equal:
Extend
[kinds]with an optionalcomplexallowlist while keeping simpleconfigurations concise:
Apply this policy:
kinds.complexis present, use it as the allowlist for complex schemaproperties;
kinds.complexis omitted, fall back tokinds.real;kinds.mapshared across categories, because kind aliases remainglobally resolved Fortran identifiers; and
type: complexproperty.The fallback permits both
real(kind=dp)andcomplex(kind=dp)withoutduplicating configuration. The explicit list permits project conventions such
as
real(kind=dp)andcomplex(kind=dpc). The two aliases may have the sameinteger value while retaining distinct names in generated source.
f2py And Python Interfaces
Python-facing APIs should use Python and NumPy complex values rather than schema
literal pairs:
For arrays, accept NumPy complex arrays through the existing array-shape
normalization path. f2py supports intrinsic Fortran complex scalars and arrays;
Python scalar arguments may use built-in
complex, while arrays normally useNumPy
complex64orcomplex128dtypes.Extend the f2py configuration with a separate complex C type table:
The
[kinds].complexfallback concerns allowed Fortran kind names only. Do notfall back from
f2py.c_types.complextof2py.c_types.real:doubleandcomplex_doubleare different f2py C representations. Require an explicitcomplex mapping for every named complex kind used by generated f2py wrappers.
No mapping is needed for an unkinded complex declaration.
Extend
F2pyKindUsage,F2pyCTypeMap, and.f2py_f2cmapgeneration with acomplexcategory. The generated map should contain entries such as:f2py also recognizes
complex_long_double, but quad-precision interoperabilityis more compiler- and platform-sensitive. Treat
complex_floatandcomplex_doubleas the primary mappings and only promise quad-precisionsupport after it passes the supported compiler matrix.
The two-element schema representation is only for JSON/YAML defaults and
examples. It should not leak into the normal Python wrapper API.
Documentation And Templates
complex, including its configured kind.(1.0, -2.0), while documenting the schema pair form.literal contains two numbers.
JSON lacks a native type.
meta-schema.
Tests
Add focused coverage for:
complex scalar;
$defsand$refreuse of complex scalar and complex-array item schemas;%reor%imassignment once the schema-aware parser supports it;wrappers; and
Run the normal project checks and update generated fixtures and examples that
gain complex fields.
Acceptance Criteria
type: complexis accepted as a first-class nml-tools scalar type.complex(kind=...)storage.have explicit, tested behavior.
[kinds].complexsupports dedicated aliases and falls back to[kinds].realwhen omitted.
[f2py.c_types.complex]mappings.complexas an intentional nml-tools dialectextension rather than strict JSON Schema.
Out Of Scope
real schema literals and Python values.