Skip to content

Commit a9f25f6

Browse files
committed
Use gridvar: namespace for input variables read from grid
1 parent 6a15205 commit a9f25f6

3 files changed

Lines changed: 47 additions & 43 deletions

File tree

manual/sphinx/user_docs/input_grids.rst

Lines changed: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ tensor is the identity matrix), but this can be changed by specifying
2828
the metric tensor components.
2929

3030
Integer quantities such as ``nx`` can be numbers (like “260”), or
31-
expressions (like “256 + 2\*MXG”).
31+
expressions (like “256 + 2\*MXG”).
3232
A common use is to make ``x`` and ``z`` dimensions have the same
3333
number of points, when ``x`` has ``mxg`` boundary cells on each
3434
boundary but ``z`` does not (since it is usually periodic):
@@ -37,14 +37,14 @@ boundary but ``z`` does not (since it is usually periodic):
3737
3838
[mesh]
3939
nx = nz + 2*mxg # X grid size
40-
nz = 256 # Z grid size
41-
mxg = 2
40+
nz = 256 # Z grid size
41+
mxg = 2
4242
4343
4444
Note that the order of the defintion within a section isn't important,
4545
variables can be used before they are defined. All variables are first
4646
read, and only processed if they are used.
47-
47+
4848
Expressions are always calculated in floating point; When expressions
4949
are used to set integer quantities (such as the number of grid
5050
points), the expressions are calculated in floating point and then
@@ -60,7 +60,7 @@ the ``round`` function:
6060
nx = 256.4 # Error!
6161
nx = round(256.4) # ok
6262
63-
63+
6464
Real (floating-point) values can also be expressions, allowing quite
6565
complicated analytic inputs. For example in the example ``test-griddata``:
6666

@@ -158,7 +158,9 @@ Cartesian.
158158
You can read additional quantities from the grid and make them available in
159159
expressions in the input file by listing them in the ``input:grid_variables``
160160
section, with the key being the name in the grid file (``mesh:file``) and the
161-
value being the type (one of ``field3d``, ``field2d``, ``boutreal``):
161+
value being the type (one of ``field3d``, ``field2d``, ``boutreal``). Variables
162+
read in this way are made available under a ``gridvar:`` namespace to avoid
163+
collisions with other input variables:
162164

163165
.. code-block:: cfg
164166
@@ -168,7 +170,7 @@ value being the type (one of ``field3d``, ``field2d``, ``boutreal``):
168170
scale = boutreal
169171
170172
[mesh]
171-
B = (scale / rho) * cos(theta)
173+
B = (gridvar:scale / gridvar:rho) * cos(gridvar:theta)
172174
173175
This section describes how to generate inputs for tokamak equilibria. If
174176
you’re not interested in tokamaks then you can skip to the next section.
@@ -219,7 +221,7 @@ From EFIT files
219221
A separate tool (in python) called `Hypnotoad <https://github.com/boutproject/hypnotoad>`_
220222
has been developed to create BOUT++ input files from R-Z equilibria. This can read EFIT ’g’
221223
(geqdsk) files, find flux surfaces, and calculate metric
222-
coefficients.
224+
coefficients.
223225

224226
From GRIDUE files
225227
--------------
@@ -281,7 +283,7 @@ As in the above code, creating an output file consists of the following steps:
281283

282284
1. Define a magnetic field
283285
2. Define the grid points. This can be broken down into:
284-
286+
285287
a) Define 2D "poloidal" grids
286288
b) Form a 3D grid by putting 2D grids together along the Y direction
287289

@@ -307,9 +309,9 @@ In this case with 10 points in y (second argument to ``rectangular_grid(nx,ny,nz
307309
the y locations are :math:`\left(0.5, 1.5, 2.5, \ldots, 9.5\right)`.
308310

309311
At each of these y locations ``rectangular_grid`` defines a rectangular 2D poloidal grid in
310-
the X-Z coordinates, by default with a length of 1 in each direction and centred on :math:`x=0,z=0`.
312+
the X-Z coordinates, by default with a length of 1 in each direction and centred on :math:`x=0,z=0`.
311313
These 2D poloidal grids are then put together into a 3D ``Grid``. This process can be customised
312-
by separating step 2 (the ``rectangular_grid`` call) into stages 2a) and 2b).
314+
by separating step 2 (the ``rectangular_grid`` call) into stages 2a) and 2b).
313315
For example, to create a periodic rectangular grid we could use the following::
314316

315317
import numpy as np
@@ -333,7 +335,7 @@ input file (this is in ``examples/zoidberg/tokamak.py``)::
333335

334336
import numpy as np
335337
import zoidberg
336-
338+
337339
field = zoidberg.field.GEQDSK("g014220.00200") # Read magnetic field
338340

339341
grid = zoidberg.grid.rectangular_grid(100, 10, 100,
@@ -345,13 +347,13 @@ input file (this is in ``examples/zoidberg/tokamak.py``)::
345347

346348
# Create the forward and backward maps
347349
maps = zoidberg.make_maps(grid, field)
348-
350+
349351
# Save to file
350352
zoidberg.write_maps(grid, field, maps, gridfile="grid.fci.nc")
351353

352354
# Plot grid points and the points they map to in the forward direction
353355
zoidberg.plot.plot_forward_map(grid, maps)
354-
356+
355357
In the last example only one poloidal grid was created (a ``RectangularPoloidalGrid``)
356358
and then re-used for each y slice. We can instead define a different grid for each y
357359
position. For example, to define a grid which expands along y (for some reason) we could do::
@@ -387,10 +389,10 @@ One way to create this grid is to define the grid points manually e.g.::
387389
r,theta = np.meshgrid(np.linspace(1,2,10),
388390
np.linspace(0,2*np.pi, 10),
389391
indexing="ij")
390-
392+
391393
R = r * np.sin(theta)
392394
Z = r * np.cos(theta)
393-
395+
394396
poloidal_grid = zoidberg.poloidal_grid.StructuredPoloidalGrid(R,Z)
395397

396398
For more complicated shapes than circles, Zoidberg comes with an
@@ -402,21 +404,21 @@ outer boundaries::
402404
inner = zoidberg.rzline.shaped_line(R0=3.0, a=0.5,
403405
elong=1.0, triang=0.0, indent=1.0,
404406
n=50)
405-
407+
406408
outer = zoidberg.rzline.shaped_line(R0=2.8, a=1.5,
407409
elong=1.0, triang=0.0, indent=0.2,
408410
n=50)
409-
411+
410412
poloidal_grid = zoidberg.poloidal_grid.grid_elliptic(inner, outer,
411413
100, 100, show=True)
412414

413415
which should produce the figure below:
414416

415417
.. figure:: ../figs/zoidberg/elliptic_grid.png
416418
:name: elliptic
417-
:alt:
419+
:alt:
418420
:scale: 50
419-
421+
420422
A grid produced by ``grid_elliptic`` from shaped inner and outer lines
421423

422424

@@ -440,14 +442,14 @@ flux surface.
440442
At the moment this will not work correctly for slab geometries, but expects
441443
closed flux surfaces such as in a stellarator or tokamak. A simple test case
442444
is a straight stellarator::
443-
445+
444446
import zoidberg
445447
field = zoidberg.field.StraightStellarator(I_coil=0.4, yperiod=10)
446448

447449
By default ``StraightStellarator`` calculates the magnetic field due to four coils which spiral around
448450
the axis at a distance :math:`r=0.8` in a classical stellarator configuration. The ``yperiod``
449451
argument is the period in y after which the coils return to their starting locations.
450-
452+
451453
To visualise the Poincare plot for this stellarator field, pass the ``MagneticField`` object
452454
to ``zoidberg.plot.plot_poincare``, together with start location(s) and periodicity information::
453455

@@ -459,14 +461,14 @@ which should produce the following figure:
459461
:name: poincare
460462
:alt: Points on four oval shaped flux surfaces in x-z at three locations along the y direction
461463
:scale: 50
462-
464+
463465
Poincare map of straight stellarator showing a single flux
464466
surface. Each colour corresponds to a different x-z plane
465-
in the y direction.
466-
467+
in the y direction.
468+
467469
The inputs here are the starting location :math:`\left(x,z\right) = \left(0.4, 0.0\right)`,
468470
and the periodicity in the y direction (10.0). By default this will
469-
integrate from this given starting location 40 times (``revs`` option) around the y domain (0 to 10).
471+
integrate from this given starting location 40 times (``revs`` option) around the y domain (0 to 10).
470472

471473
To create an ``RZline`` from these Poincare plots we need a
472474
list of points in order around the line. Since the points
@@ -476,14 +478,14 @@ this is a `known hard problem <https://en.wikipedia.org/wiki/Travelling_salesman
476478
but fortunately in this case the nearest neighbour algorithm seems to be quite robust provided there are enough points.
477479

478480
An example of calculating a Poincare plot on a single y slice (y=0) and producing an ``RZline`` is::
479-
481+
480482
from zoidberg.fieldtracer import trace_poincare
481483
rzcoord, ycoords = trace_poincare(field, 0.4, 0.0, 10.0,
482484
y_slices=[0])
483-
485+
484486
R = rzcoord[:,0,0]
485487
Z = rzcoord[:,0,1]
486-
488+
487489
line = zoidberg.rzline.line_from_points(R, Z)
488490

489491
line.plot()
@@ -495,7 +497,7 @@ approximation to the flux surface, increase the number of points by setting the
495497
(y revolutions) in the ``trace_poincare`` call.
496498

497499
In general the points along this line are not evenly
498-
distributed, but tend to cluster together in some regions and have large gaps in others.
500+
distributed, but tend to cluster together in some regions and have large gaps in others.
499501
The elliptic grid generator places grid points on the boundaries
500502
which are uniform in the index of the ``RZline`` it is given.
501503
Passing a very uneven set of points will therefore result in
@@ -509,7 +511,7 @@ to create a grid file for a straight stellarator.
509511

510512
Sections below now describe each part of Zoidberg in more detail. Further documentation
511513
of the API can be found in the docstrings and unit tests.
512-
514+
513515
Magnetic fields
514516
~~~~~~~~~~~~~~~
515517

@@ -518,12 +520,12 @@ Magnetic fields can be defined in either cylindrical or Cartesian coordinates:
518520

519521
* In Cartesian coordinates all (x,y,z) directions have the same units of length
520522
* In cylindrical coordinates the y coordinate is assumed to be an angle, so that
521-
the distance in y is given by :math:`ds = R dy` where :math:`R` is the major radius.
523+
the distance in y is given by :math:`ds = R dy` where :math:`R` is the major radius.
522524

523525
Which coordinate is used is controlled by the ``Rfunc`` method, which should return the
524526
major radius if using a cylindrical coordinate system.
525-
Should return ``None`` for a Cartesian coordinate system (the default).
526-
527+
Should return ``None`` for a Cartesian coordinate system (the default).
528+
527529
Several implementations inherit from ``MagneticField``, and provide:
528530
``Bxfunc``, ``Byfunc``, ``Bzfunc`` which give the components of the magnetic field in
529531
the x,y and z directions respectively. These should be in the same units (e.g. Tesla) for
@@ -550,14 +552,14 @@ and has a given major radius (default 1)::
550552
field = zoidberg.field.CurvedSlab()
551553

552554
Note that this uses a large aspect-ratio approximation, so the major radius
553-
is constant across the domain (independent of x).
554-
555+
is constant across the domain (independent of x).
556+
555557
Straight stellarator
556558
++++++++++++++++++++
557559

558560
This is generated by four coils with alternating currents arranged
559561
on the edge of a circle, which spiral around the axis::
560-
562+
561563
import zoidberg
562564
field = zoidberg.field.StraightStellarator()
563565

@@ -588,7 +590,7 @@ Plotting the magnetic field
588590
~~~~~~~~~~~~~~~~~~~~~~~~~~~
589591

590592
Routines to plot the magnetic field are in ``zoidberg.plot``. They include Poincare plots
591-
and 3D field line plots.
593+
and 3D field line plots.
592594

593595
For example, to make a Poincare plot from a MAST equilibrium::
594596

@@ -634,7 +636,7 @@ To create a rectangular grid, pass the number of points and lengths in the x and
634636
to ``RectangularPoloidalGrid``::
635637

636638
import zoidberg
637-
639+
638640
rect = zoidberg.poloidal_grid.RectangularPoloidalGrid( nx, nz, Lx, Lz )
639641

640642
By default the middle of the rectangle is at :math:`\left(R,Z\right) = \left(0,0\right)`
@@ -649,7 +651,7 @@ To create the structured curvilinear grids inner and outer lines are needed
649651
with the following formula:
650652

651653
.. math::
652-
654+
653655
R = R_0 - b + \left(a + b \cos\left(\theta\right)\cos\left(\theta + \delta\sin\left(\theta\right)\right)\right)
654656
655657
Z = \left(1 + \epsilon\right)a\sin\left(\theta\right)

src/field/field_factory.cxx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include <bout/utils.hxx>
3737

3838
#include "fieldgenerators.hxx"
39+
#include "fmt/format.h"
3940

4041
#include <cmath>
4142
#include <memory>
@@ -94,7 +95,8 @@ class FieldIndirect : public FieldGenerator {
9495
// Read variables from the grid file and make them available in expressions
9596
template <class T>
9697
auto add_grid_variable(FieldFactory& factory, Mesh& mesh, const std::string& name) {
97-
factory.addGenerator(name, std::make_shared<GridVariable<T>>(&mesh, name));
98+
factory.addGenerator(fmt::format("gridvar:{}", name),
99+
std::make_shared<GridVariable<T>>(&mesh, name));
98100
}
99101

100102
auto read_grid_variables(FieldFactory& factory, Mesh& mesh, Options& options) {

tests/unit/field/test_field_factory.cxx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,7 +1066,7 @@ TEST_F(FieldFactoryFieldVariableTest, CreateField3D) {
10661066
dynamic_cast<FakeMesh*>(mesh)->setGridDataSource(new GridFile{filename});
10671067
auto factory = FieldFactory{mesh, &options};
10681068

1069-
const auto output = factory.create3D("rho * cos(theta)");
1069+
const auto output = factory.create3D("gridvar:rho * cos(gridvar:theta)");
10701070
const auto x = factory.create3D("x");
10711071
EXPECT_TRUE(IsFieldEqual(output, x, "RGN_NOBNDRY", 1e-14));
10721072
}
@@ -1094,7 +1094,7 @@ TEST_F(FieldFactoryFieldVariableTest, CreateField2D) {
10941094
dynamic_cast<FakeMesh*>(mesh)->setGridDataSource(new GridFile{filename});
10951095
auto factory = FieldFactory{mesh, &options};
10961096

1097-
const auto output = factory.create2D("rho * cos(theta)");
1097+
const auto output = factory.create2D("gridvar:rho * cos(gridvar:theta)");
10981098
const auto x = factory.create2D("x");
10991099
EXPECT_TRUE(IsFieldEqual(output, x, "RGN_ALL", 1e-14));
11001100
}

0 commit comments

Comments
 (0)