Merge branch 'devel' into devel

这个提交包含在:
Craig Warren
2025-03-10 13:38:25 +00:00
提交者 GitHub
当前提交 4fe9fd52d7
共有 130 个文件被更改,包括 46198 次插入3055 次删除

查看文件

@@ -1,4 +1,4 @@
name: Bug report
name: "\U0001F41E Bug report"
description: Create a report to help us improve
labels: ["bug"]
body:
@@ -53,4 +53,4 @@ body:
label: gprMax version
placeholder: gprMax v.3.1.6
validations:
required: false
required: false

查看文件

@@ -1,7 +1,10 @@
blank_issues_enabled: false
contact_links:
- name: gprMax support
- name: "📚 Read docs"
url: https://docs.gprmax.com/en/devel/
about: Find your solution from our documenations
- name: "😊 gprMax support"
url: https://groups.google.com/g/gprmax
about: |
Please only file issues here that you believe represent actual bugs or feature requests for the gprMax software.
If you're having general trouble with using gprMax, please visit our help center to get support.
If you're having general trouble with using gprMax, please visit our help center to get support.

查看文件

@@ -1,4 +1,4 @@
name: Feature request
name: "🌟Feature request"
description: Suggest an idea for gprMax software
labels: ["feature-request"]
body:
@@ -17,4 +17,4 @@ body:
id: context
attributes:
label: Additional context
description: Add any other context about the feature request here.
description: Add any other context about the feature request here.

查看文件

@@ -1,19 +1,20 @@
# See https://pre-commit.com for more information
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.3.0
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/psf/black
rev: 23.7.0
- id: check-toml
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 23.12.0
hooks:
- id: black
args: ["--line-length", "120"] # Adjust the max line length value as needed.
- repo: https://github.com/pycqa/isort
rev: 5.12.0
rev: 5.13.2
hooks:
- id: isort
args: ["--line-length", "120", "--profile", "black"]

查看文件

@@ -5,6 +5,14 @@
# Required
version: 2
build:
os: ubuntu-22.04
tools:
python: "mambaforge-22.9"
jobs:
post_create_environment:
- python -m pip install sphinx_rtd_theme
conda:
environment: conda_env.yml
@@ -17,5 +25,4 @@ python:
sphinx:
configuration: docs/source/conf.py
formats:
- pdf

126
CODE_OF_CONDUCT.md 普通文件
查看文件

@@ -0,0 +1,126 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
codeofconduct@globalsecuritydatabase.org.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].

82
CONTRIBUTING.md 普通文件
查看文件

@@ -0,0 +1,82 @@
# Contributing to gprMax
Thank you for your interest in contributing to gprMax, we really appreciate your time and effort!
If you’re unsure where to start or how your skills fit in, reach out! You can ask us here on GitHub, by leaving a comment on a relevant issue that is already open.
Small improvements or fixes are always appreciated.
If you are new to contributing to [open source](https://opensource.guide/how-to-contribute/), this guide helps explain why, what, and how to get involved.
## How can you help us?
* Report a bug
* Improve our [documentation](https://docs.gprmax.com/en/devel/)
* Submit a bug fix
* Propose new features
* Discuss the code implementation
* Test our latest version which is available through the [devel branch](https://github.com/gprmax/gprMax/tree/devel) on our repository.
## How to Contribute
In general, we follow the "fork-and-pull" Git workflow.
1. Fork the gprMax repository
2. Clone the repository
```bash
git clone https://github.com/Your-Username/gprMax.git
```
3. Navigate to the project directory.
```bash
cd gprMax
```
4. Add a reference(remote) to the original repository.
```bash
git remote add upstream https://github.com/gprMax/gprMax.git
```
5. Check the remotes for this repository.
```bash
git remote -v
```
6. Always take a pull from the upstream repository to your devel branch to keep it at par with the main project (updated repository).
```bash
git pull upstream devel
```
7. Create a new branch.
```bash
git checkout -b <your_branch_name>
```
8. Run the following command before you commit your changes to ensure that your code is formatted correctly:
```bash
pre-commit run --all-files
```
9. Make the changes you want to make and then add
```bash
git add .
```
10. Commit your changes. To contribute to this project
```bash
git commit -m "<commit subject>"
```
11. Push your local branch to your fork
```bash
git push -u origin <your_branch_name>
```
12. Submit a Pull request so that we can review your changes
> NOTE: Be sure to merge the latest from "upstream" before making a pull request!
## Feature and Bug reports
We use GitHub issues to track bugs and features. Report them by opening a [new issue](https://github.com/gprMax/gprMax/issues).
## Code review process
The Pull Request reviews are done frequently. Try to explain your PR as much as possible using our template. Also, please make sure you respond to our feedback/questions about the PR.
## Community
Please use our [Google Group](https://groups.google.com/g/gprmax) (Forum) for comments, interaction with other users, chat, and general discussion on gprMax, GPR, and FDTD.
Checkout our website [gprmax.com](https://www.gprmax.com/) for more information and updates.

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This is the official list of entities and people who have contributed to gprMax

查看文件

@@ -117,7 +117,7 @@ Microsoft Windows
^^^^^^^^^^^^^^^^^
* Download and install Microsoft `Build Tools for Visual Studio 2022 <https://aka.ms/vs/17/release/vs_BuildTools.exe>`_ (direct link). You can also find it on the `Microsoft Visual Studio downloads page <https://visualstudio.microsoft.com/downloads/>`_ by scrolling down to the 'All Downloads' section, clicking the disclosure triangle by 'Tools for Visual Studio 2022', then clicking the download button next to 'Build Tools for Visual Studio 2022'. When installing, choose the 'Desktop development with C++' Workload and select only 'MSVC v143' and 'Windows 10 SDK' or 'Windows 11 SDK options.
* Set the Path and Environment Variables - this can be done by following the `instructions from Microsoft <https://docs.microsoft.com/en-us/cpp/build/building-on-the-command-line?view=msvc-160#developer_command_file_locations>`_, or manually by adding a form of :code:`C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.23.28105\bin\Hostx64\x64` (this may vary according to your exact machine and installation) to your system Path environment variable.
* Set the Path and Environment Variables - this can be done by following the `instructions from Microsoft <https://docs.microsoft.com/en-us/cpp/build/building-on-the-command-line?view=msvc-160#developer_command_file_locations>`_, or manually by adding a form of :code:``C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.23.28105\bin\Hostx64\x64`` (this may vary according to your exact machine and installation) to your system Path environment variable.
Alternatively, if you are using Windows 10/11 you can install the `Windows Subsystem for Linux <https://docs.microsoft.com/en-gb/windows/wsl/about>`_ and then follow the Linux install instructions for gprMax. Note however that currently, WSL does not aim to support GUI desktops or applications, e.g. Gnome, KDE, etc....
@@ -126,7 +126,7 @@ Alternatively, if you are using Windows 10/11 you can install the `Windows Subsy
Once you have installed the aforementioned tools follow these steps to build and install gprMax:
* Open a Terminal (Linux/macOS) or Command Prompt (Windows), navigate into the directory above the gprMax package, and if it is not already active, activate the gprMax conda environment :code:`conda activate gprMax`. Run the following commands:
* Open a Terminal (Linux/macOS) or Command Prompt (Windows), **navigate into the directory above the gprMax package**, and if it is not already active, activate the gprMax conda environment :code:`conda activate gprMax`. Run the following commands:
.. code-block:: bash
@@ -205,3 +205,9 @@ Periodically you should update conda and the required Python packages. With the
$ conda update conda
$ conda env update -f conda_env.yml
Thanks To Our Contributors ✨🔗
==========================
.. image:: https://contrib.rocks/image?repo=gprMax/gprMax
:target: https://github.com/gprMax/gprMax/graphs/contributors

查看文件

@@ -112,7 +112,7 @@ You should have produced an output file ``cylinder_Ascan_2D.h5``. You can view t
.. code-block:: none
python -m tools.plot_Ascan examples/cylinder_Ascan_2D.h5
python -m toolboxes.Plotting.plot_Ascan examples/cylinder_Ascan_2D.h5
:numref:`cylinder_Ascan_results` shows the time history of the electric and magnetic field components and currents at the receiver location. The :math:`E_z` field component can be converted to a voltage that represents the A-scan (trace). The initial part of the signal (~0.5-1.5 ns) represents the direct wave from transmitter to receiver. Then comes the reflected wavelet (~1.8-2.6 ns), which has opposite polarity, from the metal cylinder.
@@ -153,7 +153,7 @@ You should have produced 60 output files, one for each A-scan, with names ``cyli
.. code-block:: none
python -m tools.outputfiles_merge examples/cylinder_Bscan_2D
python -m toolboxes.Utilities.outputfiles_merge examples/cylinder_Bscan_2D
You should see a combined output file ``cylinder_Bscan_2D_merged.h5``. You can add the optional argument ``--remove-files`` if you want to automatically delete the original single A-scan output files.
@@ -161,7 +161,7 @@ You can now view an image of the B-scan using the command:
.. code-block:: none
python -m tools.plot_Bscan examples/cylinder_Bscan_2D_merged.h5 Ez
python -m toolboxes.Plotting.plot_Bscan examples/cylinder_Bscan_2D_merged.h5 Ez
:numref:`cylinder_Bscan_results` shows the B-scan (of the :math:`E_z` field component). Again, the initial part of the signal (~0.5-1.5 ns) represents the direct wave from transmitter to receiver. Then comes the reflected wave (~2-3 ns) from the metal cylinder which creates the hyperbolic shape.

查看文件

@@ -20,6 +20,10 @@ The Python API in gprMax allows users to access to gprMax functions directly fro
The syntax of the API is generally more verbose than the input file (hash) command syntax. However, for input file commands where there are an undefined number of parameters, such as adding dispersive properties, the user may find the API more manageable.
.. note::
In prior versions of gprMax (<4) the input file could be scripted using Python inserted between two commands (`#python:` and `#end_python:`). This feature is now deprecated and will be removed entirely in later versions. Users are encouraged to move to the new Python API. Antenna models can still be inserted between `#python:` and `#end_python:` commands but will need to make a small change to their input file. An example of this is provided in `examples/antenna_like_GSSI_1500_fs.in`. Alternatively a switch to the Python API can be made using the example in `examples/antenna_like_GSSI_1500_fs.py`.
Example
=======
@@ -171,6 +175,10 @@ Fractal Box
-----------
.. autoclass:: gprMax.cmds_geometry.fractal_box.FractalBox
.. note::
* Currently (2024) we are not aware of a formulation of Perfectly Matched Layer (PML) absorbing boundary that can specifically handle distributions of material properties (such as those created by fractals) throughout the thickness of the PML, i.e. this is a required area of research. Our PML formulations can work to an extent depending on your modelling scenario and requirements. You may need to increase the thickness of the PML and/or consider tuning the parameters of the PML (:ref:`pml-tuning`) to improve performance for your specific model.
Add Grass
---------
.. autoclass:: gprMax.cmds_geometry.add_grass.AddGrass
@@ -216,7 +224,7 @@ Transmission Line
Excitation File
---------------
.. autoclass:: gprMax.cmds_singleuse.ExcitationFile
.. autoclass:: gprMax.cmds_multiuse.ExcitationFile
Receiver
--------

查看文件

@@ -620,6 +620,10 @@ Allows you to introduce an orthogonal parallelepiped with fractal distributed pr
For example, to create an orthogonal parallelepiped with fractal distributed properties using a Peplinski mixing model for soil, with 50 different materials over a range of water volumetric fractions from 0.001 - 0.25, you should first define the mixing model using: ``#soil_peplinski: 0.5 0.5 2.0 2.66 0.001 0.25 my_soil`` and then specify the fractal box using ``#fractal_box: 0 0 0 0.1 0.1 0.1 1.5 1 1 1 50 my_soil my_fractal_box``.
.. note::
* Currently (2024) we are not aware of a formulation of Perfectly Matched Layer (PML) absorbing boundary that can specifically handle distributions of material properties (such as those created by fractals) throughout the thickness of the PML, i.e. this is a required area of research. Our PML formulations can work to an extent depending on your modelling scenario and requirements. You may need to increase the thickness of the PML and/or consider tuning the parameters of the PML (:ref:`pml-tuning`) to improve performance for your specific model.
#add_surface_roughness:
-----------------------
@@ -731,7 +735,7 @@ Source and output commands
#waveform:
----------
Allows you to specify waveforms to use with sources in the model. The syntax of the command is:
Allows you to specify common waveform shapes to use with sources in the model. The syntax of the command is:
.. code-block:: none
@@ -757,7 +761,7 @@ For example, to specify the normalised first derivative of a Gaussian waveform w
.. note::
* The functions used to create the waveforms can be found in the in ``toolboxes/Plotting`` package.
* The functions used to create the waveforms can be found in the ``toolboxes/Plotting`` package.
* ``gaussiandot``, ``gaussiandotnorm``, ``gaussiandotdot``, ``gaussiandotdotnorm``, ``ricker`` waveforms have their centre frequencies specified by the user, i.e. they are not derived to the 'base' ``gaussian``
* ``gaussianprime`` and ``gaussiandoubleprime`` waveforms are the first derivative and second derivative of the 'base' ``gaussian`` waveform, i.e. the centre frequencies of the waveforms will rise for the first and second derivatives.
@@ -765,11 +769,17 @@ For example, to specify the normalised first derivative of a Gaussian waveform w
#excitation_file:
-----------------
Allows you to specify an ASCII file that contains columns of amplitude values that specify custom waveform shapes that can be used with sources in the model.
Allows you to specify an ASCII file that contains amplitude values that specify custom waveform(s) that can be used with sources in the model.
The first row of each column must begin with a identifier string that will be used as the name of each waveform. Optionally, the first column of the file may contain a time vector of values (which must use the identifier ``time``) to interpolate the amplitude values of the waveform. If a time vector is not given, a vector of time values corresponding to the simulation time step and number of iterations will be used.
The first row of each column must begin with a identifier string that will be used as the name of each waveform. Subsequent rows should contain amplitude values for the custom waveform you want to use. You can import multiple different waveforms (as columns of amplitude data) in a single file.
If there are less amplitude values than the number of iterations that are going to be performed, the end of the sequence of amplitude values will be padded with zero values up to the number of iterations. If extra amplitude values are specified than needed then they are ignored. The syntax of the command is:
Ideally, there should be the same number of amplitude values as number of iterations in your model. If there are less amplitude values than the number of iterations in the model, the end of the sequence of amplitude values will be padded with zero values up to the number of iterations. If extra amplitude values are specified than needed then they are ignored.
Optionally, in the first column of the file you may specify your own time vector of values (which must use the identifier ``time``) to use with the amplitude values of the waveform.
The amplitude values will be interpolated using either the aforementioned user specified time vector, or if none was supplied, a vector of time values corresponding to the simulation time step and number of iterations will be used. Key parameters used for the interpolation can be specified in the command.
The syntax of the command is:
.. code-block:: none
@@ -965,7 +975,7 @@ For example to save a snapshot of the electromagnetic fields in the model at a s
PML commands
============
The default behaviour for the absorbing boundary conditions (ABC) is first order Complex Frequency Shifted (CFS) Perfectly Matched Layers (PML), with thicknesses of 10 cells on each of the six sides of the model domain. The thickness of the PML can be altered by using the following command (further customisation of the PML is possible using our Python API):
The default behaviour for the absorbing boundary conditions (ABC) is first order Complex Frequency Shifted (CFS) Perfectly Matched Layers (PML), with thicknesses of 10 cells on each of the six sides of the model domain.
#pml_cells:
------------
@@ -989,3 +999,47 @@ For example to use a PML with 20 cells (thicker than the default 10 cells) on on
.. code-block:: none
#pml_cells: 10 10 20 10 10 20
#pml_formulation:
-----------------
Allows you to alter the formulation used for the PML. The current options are to use the Higher Order RIPML (HORIPML) - https://doi.org/10.1109/TAP.2011.2180344, or Multipole RIPML (MRIPML) - https://doi.org/10.1109/TAP.2018.2823864. The syntax of the command is:
.. code-block:: none
#pml_formulation: str
* ``str`` can be either 'HORIPML' or 'MRIPML'
For example to use the Multipole RIPML:
.. code-block:: none
#pml_formulation: MRIPML
#pml_cfs:
---------
Allows you (advanced) control of the parameters that are used to build each order of the PML. Up to a second order PML can currently be specified, i.e. by using two ``#pml_cfs`` commands. The syntax of the command is:
.. code-block:: none
#pml_cfs: str1 str2 f1 f2 str3 str4 f3 f4 str5 str6 f5 f6
* ``str1`` is the type of scaling to use for the CFS :math:`\alpha` parameter. It can be ``constant``, ``linear``, ``quadratic``, ``cubic``, ``quartic``, ``quintic`` and ``sextic``.
* ``str2`` is the direction of the scaling to use for the CFS :math:`\alpha` parameter. It can be ``forward`` or ``reverse``.
* ``f1 f2`` are the minimum and maximum values for the CFS :math:`\alpha` parameter.
* ``str3`` is the type of scaling to use for the CFS :math:`\kappa` parameter. It can be ``constant``, ``linear``, ``quadratic``, ``cubic``, ``quartic``, ``quintic`` and ``sextic``.
* ``str4`` is the direction of the scaling to use for the CFS :math:`\kappa` parameter. It can be ``forward`` or ``reverse``.
* ``f3 f4`` are the minimum and maximum values for the CFS :math:`\kappa` parameter. The minimum value for the CFS :math:`\kappa` parameter is one.
* ``str5`` is the type of scaling to use for the CFS :math:`\sigma` parameter. It can be ``constant``, ``linear``, ``quadratic``, ``cubic``, ``quartic``, ``quintic`` and ``sextic``.
* ``str6`` is the direction of the scaling to use for the CFS :math:`\sigma` parameter. It can be ``forward`` or ``reverse``.
* ``f5 f6`` are the minimum and maximum values for the CFS :math:`\sigma` parameter.
The CFS values (which are internally specified) used for the default standard first order PML are: ``#pml_cfs: constant forward 0 0 constant forward 1 1 quartic forward 0 None``. Specifying 'None' for the maximum value of :math:`\sigma` forces gprMax to calculate it internally based on the relative permittivity and permeability of the underlying materials in the model.
The parameters will be applied to all slabs of the PML that are switched on.
.. tip::
``forward`` direction implies minimum parameter value at the inner boundary of the PML and maximum parameter value at the edge of computational domain, ``reverse`` is the opposite.

查看文件

@@ -0,0 +1,11 @@
#title: GSSI 1.5GHz 'like' antenna in free-space
#domain: 0.250 0.188 0.220
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 6e-9
#python:
from toolboxes.GPRAntennaModels.GSSI import antenna_like_GSSI_1500
gssi_objects = antenna_like_GSSI_1500(0.125, 0.094, 0.100, resolution=0.001)
for obj in gssi_objects:
print(obj.__str__())
#end_python:

查看文件

@@ -0,0 +1,60 @@
"""An antenna model similar to a GSSI 400MHz antenna in free space
This example model demonstrates how to use one of the built-in antenna models.
The geometry is 3D and the domain filled with freespace (the default). The
antenna model method is imported from its toolbox and the objects that build the
antenna are iteratively added to the scene. The antenna can be rotated if
desired, by rotating the objects that it is built from before they are added to
the scene.
"""
from pathlib import Path
import gprMax
from toolboxes.GPRAntennaModels.GSSI import antenna_like_GSSI_400
# File path for output
fn = Path(__file__)
# Discretisation
dl = 0.002
# Domain
x = 0.340
y = 0.340
z = 0.318
scene = gprMax.Scene()
title = gprMax.Title(name=fn.with_suffix("").name)
domain = gprMax.Domain(p1=(x, y, z))
dxdydz = gprMax.Discretisation(p1=(dl, dl, dl))
time_window = gprMax.TimeWindow(time=15e-9)
scene.add(title)
scene.add(domain)
scene.add(dxdydz)
scene.add(time_window)
# Import antenna model and add to model
ant_pos = (0.170, 0.170, 0.100)
gssi_objects = antenna_like_GSSI_400(ant_pos[0], ant_pos[1], ant_pos[2], resolution=dl)
for obj in gssi_objects:
scene.add(obj)
gv1 = gprMax.GeometryView(
p1=(0, 0, 0), p2=(x, y, z), dl=(dl, dl, dl), filename="antenna_like_GSSI_400", output_type="n"
)
gv2 = gprMax.GeometryView(
p1=(ant_pos[0] - 0.150, ant_pos[1] - 0.150, ant_pos[2]),
p2=(ant_pos[0] + 0.150, ant_pos[1] + 0.150, ant_pos[2] + 0.050),
dl=(dl, dl, dl),
filename="antenna_like_GSSI_400_pcb",
output_type="f",
)
# scene.add(gv1)
# scene.add(gv2)
# Run model
gprMax.run(scenes=[scene], geometry_only=False, outputfile=fn, gpu=None)

查看文件

@@ -155,4 +155,4 @@ gv1 = gprMax.GeometryView(
)
scene.add(gv1)
gprMax.run(scenes=[scene], n=1, geometry_only=True, outputfile=fn, subgrid=True, autotranslate=True)
gprMax.run(scenes=[scene], n=1, geometry_only=False, outputfile=fn, subgrid=True, autotranslate=True)

查看文件

@@ -1,4 +0,0 @@
[build-system]
requires = ['setuptools', 'wheel', 'numpy>=1.19.0', 'Cython>=0.29.21',
'jinja2']
build-backend = "setuptools.build_meta"

查看文件

@@ -14,9 +14,11 @@ from .cmds_geometry.add_grass import AddGrass
from .cmds_geometry.add_surface_roughness import AddSurfaceRoughness
from .cmds_geometry.add_surface_water import AddSurfaceWater
from .cmds_geometry.box import Box
from .cmds_geometry.cone import Cone
from .cmds_geometry.cylinder import Cylinder
from .cmds_geometry.cylindrical_sector import CylindricalSector
from .cmds_geometry.edge import Edge
from .cmds_geometry.ellipsoid import Ellipsoid
from .cmds_geometry.fractal_box import FractalBox
from .cmds_geometry.geometry_objects_read import GeometryObjectsRead
from .cmds_geometry.plate import Plate
@@ -27,6 +29,7 @@ from .cmds_multiuse import (
AddDebyeDispersion,
AddDrudeDispersion,
AddLorentzDispersion,
ExcitationFile,
GeometryObjectsWrite,
GeometryView,
HertzianDipole,
@@ -38,6 +41,7 @@ from .cmds_multiuse import (
RxArray,
Snapshot,
SoilPeplinski,
Subgrid,
TransmissionLine,
VoltageSource,
Waveform,
@@ -47,8 +51,8 @@ from .cmds_multiuse import DiscretePlaneWave
from .cmds_singleuse import (
Discretisation,
Domain,
ExcitationFile,
OMPThreads,
OutputDir,
PMLProps,
RxSteps,
SrcSteps,
@@ -57,7 +61,6 @@ from .cmds_singleuse import (
Title,
)
from .gprMax import run as run
from .hash_cmds_file import user_libs_fn_to_scene_obj
from .scene import Scene
from .subgrids.user_objects import SubGridHSG

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -64,7 +64,7 @@ class AddGrass(UserObjectGeometry):
self.kwargs["p1"] = tuple(rot_pts[0, :])
self.kwargs["p2"] = tuple(rot_pts[1, :])
def create(self, grid, uip):
def build(self, grid, uip):
"""Add Grass to fractal box."""
try:
p1 = self.kwargs["p1"]
@@ -78,19 +78,28 @@ class AddGrass(UserObjectGeometry):
raise
try:
seed = self.kwargs["seed"]
seed = int(self.kwargs["seed"])
except KeyError:
logger.warning(
f"{self.__str__()} no value for seed detected. This "
"means you will get a different fractal distribution "
"every time the model runs."
)
seed = None
if self.do_rotate:
self._do_rotate()
# Get the correct fractal volume
volumes = [volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id]
volumes = [
volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id
]
try:
volume = volumes[0]
except NameError:
logger.exception(f"{self.__str__()} cannot find FractalBox {fractal_box_id}")
logger.exception(
f"{self.__str__()} cannot find FractalBox {fractal_box_id}"
)
raise
p1, p2 = uip.check_box_points(p1, p2, self.__str__())
@@ -98,27 +107,36 @@ class AddGrass(UserObjectGeometry):
xf, yf, zf = p2
if frac_dim < 0:
logger.exception(f"{self.__str__()} requires a positive value for " + "the fractal dimension")
logger.exception(
f"{self.__str__()} requires a positive value for the fractal dimension"
)
raise ValueError
if limits[0] < 0 or limits[1] < 0:
logger.exception(
f"{self.__str__()} requires a positive value for " + "the minimum and maximum heights for grass blades"
f"{self.__str__()} requires a positive value for the minimum and maximum heights for grass blades"
)
raise ValueError
# Check for valid orientations
if xs == xf:
if ys == yf or zs == zf:
logger.exception(f"{self.__str__()} dimensions are not specified correctly")
logger.exception(
f"{self.__str__()} dimensions are not specified correctly"
)
raise ValueError
if xs not in [volume.xs, volume.xf]:
logger.exception(f"{self.__str__()} must specify external surfaces on a fractal box")
logger.exception(
f"{self.__str__()} must specify external surfaces on a fractal box"
)
raise ValueError
fractalrange = (round_value(limits[0] / grid.dx), round_value(limits[1] / grid.dx))
fractalrange = (
round_value(limits[0] / grid.dx),
round_value(limits[1] / grid.dx),
)
# xminus surface
if xs == volume.xs:
logger.exception(
f"{self.__str__()} grass can only be specified " + "on surfaces in the positive axis direction"
f"{self.__str__()} grass can only be specified on surfaces in the positive axis direction"
)
raise ValueError
# xplus surface
@@ -126,24 +144,31 @@ class AddGrass(UserObjectGeometry):
if fractalrange[1] > grid.nx:
logger.exception(
f"{self.__str__()} cannot apply grass to "
+ "fractal box as it would exceed the domain "
+ "size in the x direction"
"fractal box as it would exceed the domain "
"size in the x direction"
)
raise ValueError
requestedsurface = "xplus"
elif ys == yf:
if zs == zf:
logger.exception(f"{self.__str__()} dimensions are not specified correctly")
logger.exception(
f"{self.__str__()} dimensions are not specified correctly"
)
raise ValueError
if ys not in [volume.ys, volume.yf]:
logger.exception(f"{self.__str__()} must specify external surfaces on a fractal box")
logger.exception(
f"{self.__str__()} must specify external surfaces on a fractal box"
)
raise ValueError
fractalrange = (round_value(limits[0] / grid.dy), round_value(limits[1] / grid.dy))
fractalrange = (
round_value(limits[0] / grid.dy),
round_value(limits[1] / grid.dy),
)
# yminus surface
if ys == volume.ys:
logger.exception(
f"{self.__str__()} grass can only be specified " + "on surfaces in the positive axis direction"
f"{self.__str__()} grass can only be specified on surfaces in the positive axis direction"
)
raise ValueError
# yplus surface
@@ -151,21 +176,26 @@ class AddGrass(UserObjectGeometry):
if fractalrange[1] > grid.ny:
logger.exception(
f"{self.__str__()} cannot apply grass to "
+ "fractal box as it would exceed the domain "
+ "size in the y direction"
"fractal box as it would exceed the domain "
"size in the y direction"
)
raise ValueError
requestedsurface = "yplus"
elif zs == zf:
if zs not in [volume.zs, volume.zf]:
logger.exception(f"{self.__str__()} must specify external surfaces on a fractal box")
logger.exception(
f"{self.__str__()} must specify external surfaces on a fractal box"
)
raise ValueError
fractalrange = (round_value(limits[0] / grid.dz), round_value(limits[1] / grid.dz))
fractalrange = (
round_value(limits[0] / grid.dz),
round_value(limits[1] / grid.dz),
)
# zminus surface
if zs == volume.zs:
logger.exception(
f"{self.__str__()} grass can only be specified " + "on surfaces in the positive axis direction"
f"{self.__str__()} grass can only be specified on surfaces in the positive axis direction"
)
raise ValueError
# zplus surface
@@ -173,8 +203,8 @@ class AddGrass(UserObjectGeometry):
if fractalrange[1] > grid.nz:
logger.exception(
f"{self.__str__()} cannot apply grass to "
+ "fractal box as it would exceed the domain "
+ "size in the z direction"
"fractal box as it would exceed the domain "
"size in the z direction"
)
raise ValueError
requestedsurface = "zplus"
@@ -183,10 +213,9 @@ class AddGrass(UserObjectGeometry):
logger.exception(f"{self.__str__()} dimensions are not specified correctly")
raise ValueError
surface = FractalSurface(xs, xf, ys, yf, zs, zf, frac_dim)
surface = FractalSurface(xs, xf, ys, yf, zs, zf, frac_dim, seed)
surface.ID = "grass"
surface.surfaceID = requestedsurface
surface.seed = seed
# Set the fractal range to scale the fractal distribution between zero and one
surface.fractalrange = (0, 1)
@@ -195,7 +224,7 @@ class AddGrass(UserObjectGeometry):
if n_blades > surface.fractalsurface.shape[0] * surface.fractalsurface.shape[1]:
logger.exception(
f"{self.__str__()} the specified surface is not large "
+ "enough for the number of grass blades/roots specified"
"enough for the number of grass blades/roots specified"
)
raise ValueError
@@ -215,7 +244,8 @@ class AddGrass(UserObjectGeometry):
# probability values, and convert the 1D index back into a x, y index
# for the original surface.
bladesindex = np.unravel_index(
np.digitize(A, probability1D), (surface.fractalsurface.shape[0], surface.fractalsurface.shape[1])
np.digitize(A, probability1D),
(surface.fractalsurface.shape[0], surface.fractalsurface.shape[1]),
)
# Set the fractal range to minimum and maximum heights of the grass blades
@@ -223,15 +253,16 @@ class AddGrass(UserObjectGeometry):
# Set the fractal surface using the pre-calculated spatial distribution
# and a random height
surface.fractalsurface = np.zeros((surface.fractalsurface.shape[0], surface.fractalsurface.shape[1]))
surface.fractalsurface = np.zeros(
(surface.fractalsurface.shape[0], surface.fractalsurface.shape[1])
)
for i in range(len(bladesindex[0])):
surface.fractalsurface[bladesindex[0][i], bladesindex[1][i]] = R.randint(
surface.fractalrange[0], surface.fractalrange[1], size=1
)
# Create grass geometry parameters
g = Grass(n_blades)
g.seed = surface.seed
g = Grass(n_blades, surface.seed)
surface.grass.append(g)
# Check to see if grass has been already defined as a material
@@ -244,7 +275,7 @@ class AddGrass(UserObjectGeometry):
if testgrass:
logger.exception(
f"{self.__str__()} requires the time step for the "
+ "model to be less than the relaxation time required to model grass."
"model to be less than the relaxation time required to model grass."
)
raise ValueError
@@ -252,9 +283,9 @@ class AddGrass(UserObjectGeometry):
logger.info(
f"{self.grid_name(grid)}{n_blades} blades of grass on surface from "
+ f"{xs * grid.dx:g}m, {ys * grid.dy:g}m, {zs * grid.dz:g}m, "
+ f"to {xf * grid.dx:g}m, {yf * grid.dy:g}m, {zf * grid.dz:g}m "
+ f"with fractal dimension {surface.dimension:g}, fractal seeding "
+ f"{surface.seed}, and range {limits[0]:g}m to {limits[1]:g}m, "
+ f"added to {surface.operatingonID}."
f"{xs * grid.dx:g}m, {ys * grid.dy:g}m, {zs * grid.dz:g}m, "
f"to {xf * grid.dx:g}m, {yf * grid.dy:g}m, {zf * grid.dz:g}m "
f"with fractal dimension {surface.dimension:g}, fractal seeding "
f"{surface.seed}, and range {limits[0]:g}m to {limits[1]:g}m, "
f"added to {surface.operatingonID}."
)

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -65,7 +65,7 @@ class AddSurfaceRoughness(UserObjectGeometry):
self.kwargs["p1"] = tuple(rot_pts[0, :])
self.kwargs["p2"] = tuple(rot_pts[1, :])
def create(self, grid, uip):
def build(self, grid, uip):
try:
p1 = self.kwargs["p1"]
p2 = self.kwargs["p2"]
@@ -78,12 +78,12 @@ class AddSurfaceRoughness(UserObjectGeometry):
raise
try:
seed = self.kwargs["seed"]
seed = int(self.kwargs["seed"])
except KeyError:
logger.warning(
f"{self.__str__()} no value for seed detected. This "
+ "means you will get a different fractal distribution "
+ "every time the model runs."
"means you will get a different fractal distribution "
"every time the model runs."
)
seed = None
@@ -91,11 +91,15 @@ class AddSurfaceRoughness(UserObjectGeometry):
self._do_rotate()
# Get the correct fractal volume
volumes = [volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id]
volumes = [
volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id
]
if volumes:
volume = volumes[0]
else:
logger.exception(f"{self.__str__()} cannot find FractalBox {fractal_box_id}")
logger.exception(
f"{self.__str__()} cannot find FractalBox {fractal_box_id}"
)
raise ValueError
p1, p2 = uip.check_box_points(p1, p2, self.__str__())
@@ -103,38 +107,48 @@ class AddSurfaceRoughness(UserObjectGeometry):
xf, yf, zf = p2
if frac_dim < 0:
logger.exception(f"{self.__str__()} requires a positive value for the " + "fractal dimension")
logger.exception(
f"{self.__str__()} requires a positive value for the "
+ "fractal dimension"
)
raise ValueError
if weighting[0] < 0:
logger.exception(
f"{self.__str__()} requires a positive value for the "
+ "fractal weighting in the first direction of the surface"
"fractal weighting in the first direction of the surface"
)
raise ValueError
if weighting[1] < 0:
logger.exception(
f"{self.__str__()} requires a positive value for the "
+ "fractal weighting in the second direction of the surface"
"fractal weighting in the second direction of the surface"
)
raise ValueError
# Check for valid orientations
if xs == xf:
if ys == yf or zs == zf:
logger.exception(f"{self.__str__()} dimensions are not specified correctly")
logger.exception(
f"{self.__str__()} dimensions are not specified correctly"
)
raise ValueError
if xs not in [volume.xs, volume.xf]:
logger.exception(f"{self.__str__()} can only be used on the external " + "surfaces of a fractal box")
logger.exception(
f"{self.__str__()} can only be used on the external surfaces of a fractal box"
)
raise ValueError
fractalrange = (round_value(limits[0] / grid.dx), round_value(limits[1] / grid.dx))
fractalrange = (
round_value(limits[0] / grid.dx),
round_value(limits[1] / grid.dx),
)
# xminus surface
if xs == volume.xs:
if fractalrange[0] < 0 or fractalrange[1] > volume.xf:
logger.exception(
f"{self.__str__()} cannot apply fractal surface "
+ "to fractal box as it would exceed either the "
+ "upper coordinates of the fractal box or the "
+ "domain in the x direction"
"to fractal box as it would exceed either the "
"upper coordinates of the fractal box or the "
"domain in the x direction"
)
raise ValueError
requestedsurface = "xminus"
@@ -143,29 +157,37 @@ class AddSurfaceRoughness(UserObjectGeometry):
if fractalrange[0] < volume.xs or fractalrange[1] > grid.nx:
logger.exception(
f"{self.__str__()} cannot apply fractal surface "
+ "to fractal box as it would exceed either the "
+ "lower coordinates of the fractal box or the "
+ "domain in the x direction"
"to fractal box as it would exceed either the "
"lower coordinates of the fractal box or the "
"domain in the x direction"
)
raise ValueError
requestedsurface = "xplus"
elif ys == yf:
if zs == zf:
logger.exception(f"{self.__str__()} dimensions are not specified correctly")
logger.exception(
f"{self.__str__()} dimensions are not specified correctly"
)
raise ValueError
if ys not in [volume.ys, volume.yf]:
logger.exception(f"{self.__str__()} can only be used on the external " + "surfaces of a fractal box")
logger.exception(
f"{self.__str__()} can only be used on the external "
+ "surfaces of a fractal box"
)
raise ValueError
fractalrange = (round_value(limits[0] / grid.dy), round_value(limits[1] / grid.dy))
fractalrange = (
round_value(limits[0] / grid.dy),
round_value(limits[1] / grid.dy),
)
# yminus surface
if ys == volume.ys:
if fractalrange[0] < 0 or fractalrange[1] > volume.yf:
logger.exception(
f"{self.__str__()} cannot apply fractal surface "
+ "to fractal box as it would exceed either the "
+ "upper coordinates of the fractal box or the "
+ "domain in the y direction"
"to fractal box as it would exceed either the "
"upper coordinates of the fractal box or the "
"domain in the y direction"
)
raise ValueError
requestedsurface = "yminus"
@@ -174,26 +196,32 @@ class AddSurfaceRoughness(UserObjectGeometry):
if fractalrange[0] < volume.ys or fractalrange[1] > grid.ny:
logger.exception(
f"{self.__str__()} cannot apply fractal surface "
+ "to fractal box as it would exceed either the "
+ "lower coordinates of the fractal box or the "
+ "domain in the y direction"
"to fractal box as it would exceed either the "
"lower coordinates of the fractal box or the "
"domain in the y direction"
)
raise ValueError
requestedsurface = "yplus"
elif zs == zf:
if zs not in [volume.zs, volume.zf]:
logger.exception(f"{self.__str__()} can only be used on the external " + "surfaces of a fractal box")
logger.exception(
f"{self.__str__()} can only be used on the external "
+ "surfaces of a fractal box"
)
raise ValueError
fractalrange = (round_value(limits[0] / grid.dz), round_value(limits[1] / grid.dz))
fractalrange = (
round_value(limits[0] / grid.dz),
round_value(limits[1] / grid.dz),
)
# zminus surface
if zs == volume.zs:
if fractalrange[0] < 0 or fractalrange[1] > volume.zf:
logger.exception(
f"{self.__str__()} cannot apply fractal surface "
+ "to fractal box as it would exceed either the "
+ "upper coordinates of the fractal box or the "
+ "domain in the x direction"
"to fractal box as it would exceed either the "
"upper coordinates of the fractal box or the "
"domain in the x direction"
)
raise ValueError
requestedsurface = "zminus"
@@ -202,9 +230,9 @@ class AddSurfaceRoughness(UserObjectGeometry):
if fractalrange[0] < volume.zs or fractalrange[1] > grid.nz:
logger.exception(
f"{self.__str__()} cannot apply fractal surface "
+ "to fractal box as it would exceed either the "
+ "lower coordinates of the fractal box or the "
+ "domain in the z direction"
"to fractal box as it would exceed either the "
"lower coordinates of the fractal box or the "
"domain in the z direction"
)
raise ValueError
requestedsurface = "zplus"
@@ -213,20 +241,18 @@ class AddSurfaceRoughness(UserObjectGeometry):
logger.exception(f"{self.__str__()} dimensions are not specified correctly")
raise ValueError
surface = FractalSurface(xs, xf, ys, yf, zs, zf, frac_dim)
surface = FractalSurface(xs, xf, ys, yf, zs, zf, frac_dim, seed)
surface.surfaceID = requestedsurface
surface.fractalrange = fractalrange
surface.operatingonID = volume.ID
if seed is not None:
surface.seed = int(seed)
else:
surface.seed = seed
surface.weighting = weighting
# List of existing surfaces IDs
existingsurfaceIDs = [x.surfaceID for x in volume.fractalsurfaces]
if surface.surfaceID in existingsurfaceIDs:
logger.exception(f"{self.__str__()} has already been used on the " + f"{surface.surfaceID} surface")
logger.exception(
f"{self.__str__()} has already been used on the {surface.surfaceID} surface"
)
raise ValueError
surface.generate_fractal_surface()
@@ -234,10 +260,10 @@ class AddSurfaceRoughness(UserObjectGeometry):
logger.info(
f"{self.grid_name(grid)}Fractal surface from {xs * grid.dx:g}m, "
+ f"{ys * grid.dy:g}m, {zs * grid.dz:g}m, to {xf * grid.dx:g}m, "
+ f"{yf * grid.dy:g}m, {zf * grid.dz:g}m with fractal dimension "
+ f"{surface.dimension:g}, fractal weightings {surface.weighting[0]:g}, "
+ f"{surface.weighting[1]:g}, fractal seeding {surface.seed}, "
+ f"and range {limits[0]:g}m to {limits[1]:g}m, added to "
+ f"{surface.operatingonID}."
f"{ys * grid.dy:g}m, {zs * grid.dz:g}m, to {xf * grid.dx:g}m, "
f"{yf * grid.dy:g}m, {zf * grid.dz:g}m with fractal dimension "
f"{surface.dimension:g}, fractal weightings {surface.weighting[0]:g}, "
f"{surface.weighting[1]:g}, fractal seeding {surface.seed}, "
f"and range {limits[0]:g}m to {limits[1]:g}m, added to "
f"{surface.operatingonID}."
)

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -60,7 +60,7 @@ class AddSurfaceWater(UserObjectGeometry):
self.kwargs["p1"] = tuple(rot_pts[0, :])
self.kwargs["p2"] = tuple(rot_pts[1, :])
def create(self, grid, uip):
def build(self, grid, uip):
""" "Create surface water on fractal box."""
try:
p1 = self.kwargs["p1"]
@@ -74,10 +74,14 @@ class AddSurfaceWater(UserObjectGeometry):
if self.do_rotate:
self._do_rotate()
if volumes := [volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id]:
if volumes := [
volume for volume in grid.fractalvolumes if volume.ID == fractal_box_id
]:
volume = volumes[0]
else:
logger.exception(f"{self.__str__()} cannot find FractalBox {fractal_box_id}")
logger.exception(
f"{self.__str__()} cannot find FractalBox {fractal_box_id}"
)
raise ValueError
p1, p2 = uip.check_box_points(p1, p2, self.__str__())
@@ -85,16 +89,22 @@ class AddSurfaceWater(UserObjectGeometry):
xf, yf, zf = p2
if depth <= 0:
logger.exception(f"{self.__str__()} requires a positive value for the " + f"depth of water")
logger.exception(
f"{self.__str__()} requires a positive value for the depth of water"
)
raise ValueError
# Check for valid orientations
if xs == xf:
if ys == yf or zs == zf:
logger.exception(f"{self.__str__()} dimensions are not specified correctly")
logger.exception(
f"{self.__str__()} dimensions are not specified correctly"
)
raise ValueError
if xs not in [volume.xs, volume.xf]:
logger.exception(f"{self.__str__()} can only be used on the external surfaces " f"of a fractal box")
logger.exception(
f"{self.__str__()} can only be used on the external surfaces of a fractal box"
)
raise ValueError
# xminus surface
if xs == volume.xs:
@@ -107,10 +117,14 @@ class AddSurfaceWater(UserObjectGeometry):
elif ys == yf:
if zs == zf:
logger.exception(f"{self.__str__()} dimensions are not specified correctly")
logger.exception(
f"{self.__str__()} dimensions are not specified correctly"
)
raise ValueError
if ys not in [volume.ys, volume.yf]:
logger.exception(f"{self.__str__()} can only be used on the external surfaces " + f"of a fractal box")
logger.exception(
f"{self.__str__()} can only be used on the external surfaces of a fractal box"
)
raise ValueError
# yminus surface
if ys == volume.ys:
@@ -123,7 +137,9 @@ class AddSurfaceWater(UserObjectGeometry):
elif zs == zf:
if zs not in [volume.zs, volume.zf]:
logger.exception(f"{self.__str__()} can only be used on the external surfaces " f"of a fractal box")
logger.exception(
f"{self.__str__()} can only be used on the external surfaces of a fractal box"
)
raise ValueError
# zminus surface
if zs == volume.zs:
@@ -138,20 +154,25 @@ class AddSurfaceWater(UserObjectGeometry):
logger.exception(f"{self.__str__()} dimensions are not specified correctly")
raise ValueError
surface = next((x for x in volume.fractalsurfaces if x.surfaceID == requestedsurface), None)
surface = next(
(x for x in volume.fractalsurfaces if x.surfaceID == requestedsurface), None
)
if not surface:
logger.exception(
f"{self.__str__()} specified surface {requestedsurface} " + f"does not have a rough surface applied"
f"{self.__str__()} specified surface {requestedsurface} does not have a rough surface applied"
)
raise ValueError
surface.filldepth = filldepthcells
# Check that requested fill depth falls within range of surface roughness
if surface.filldepth < surface.fractalrange[0] or surface.filldepth > surface.fractalrange[1]:
if (
surface.filldepth < surface.fractalrange[0]
or surface.filldepth > surface.fractalrange[1]
):
logger.exception(
f"{self.__str__()} requires a value for the depth of water that lies with the "
+ f"range of the requested surface roughness"
f"range of the requested surface roughness"
)
raise ValueError
@@ -170,7 +191,7 @@ class AddSurfaceWater(UserObjectGeometry):
logger.info(
f"{self.grid_name(grid)}Water on surface from {xs * grid.dx:g}m, "
+ f"{ys * grid.dy:g}m, {zs * grid.dz:g}m, to {xf * grid.dx:g}m, "
+ f"{yf * grid.dy:g}m, {zf * grid.dz:g}m with depth {filldepth:g}m, "
+ f"added to {surface.operatingonID}."
f"{ys * grid.dy:g}m, {zs * grid.dz:g}m, to {xf * grid.dx:g}m, "
f"{yf * grid.dy:g}m, {zf * grid.dz:g}m with depth {filldepth:g}m, "
f"added to {surface.operatingonID}."
)

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -24,7 +24,7 @@ import gprMax.config as config
from ..cython.geometry_primitives import build_box
from ..materials import Material
from .cmds_geometry import UserObjectGeometry, rotate_2point_object
from .cmds_geometry import UserObjectGeometry, check_averaging, rotate_2point_object
logger = logging.getLogger(__name__)
@@ -60,7 +60,7 @@ class Box(UserObjectGeometry):
self.kwargs["p1"] = tuple(rot_pts[0, :])
self.kwargs["p2"] = tuple(rot_pts[1, :])
def create(self, grid, uip):
def build(self, grid, uip):
try:
p1 = self.kwargs["p1"]
p2 = self.kwargs["p2"]
@@ -87,6 +87,7 @@ class Box(UserObjectGeometry):
try:
# Try user-specified averaging
averagebox = self.kwargs["averaging"]
averagebox = check_averaging(averagebox)
except KeyError:
# Otherwise go with the grid default
averagebox = grid.averagevolumeobjects
@@ -126,10 +127,18 @@ class Box(UserObjectGeometry):
m = Material(numID, requiredID)
m.type = "dielectric-smoothed"
# Create dielectric-smoothed constituents for material
m.er = np.mean((materials[0].er, materials[1].er, materials[2].er), axis=0)
m.se = np.mean((materials[0].se, materials[1].se, materials[2].se), axis=0)
m.mr = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0)
m.sm = np.mean((materials[0].sm, materials[1].sm, materials[2].sm), axis=0)
m.er = np.mean(
(materials[0].er, materials[1].er, materials[2].er), axis=0
)
m.se = np.mean(
(materials[0].se, materials[1].se, materials[2].se), axis=0
)
m.mr = np.mean(
(materials[0].mr, materials[1].mr, materials[2].mr), axis=0
)
m.sm = np.mean(
(materials[0].sm, materials[1].sm, materials[2].sm), axis=0
)
# Append the new material object to the materials list
grid.materials.append(m)
@@ -141,7 +150,6 @@ class Box(UserObjectGeometry):
yf,
zs,
zf,
config.get_model_config().ompthreads,
numID,
numIDx,
numIDy,
@@ -157,7 +165,7 @@ class Box(UserObjectGeometry):
logger.info(
f"{self.grid_name(grid)}Box from {p5[0]:g}m, {p5[1]:g}m, "
+ f"{p5[2]:g}m, to {p6[0]:g}m, {p6[1]:g}m, {p6[2]:g}m of "
+ f"material(s) {', '.join(materialsrequested)} created, "
+ f"dielectric smoothing is {dielectricsmoothing}."
f"{p5[2]:g}m, to {p6[0]:g}m, {p6[1]:g}m, {p6[2]:g}m of "
f"material(s) {', '.join(materialsrequested)} created, "
f"dielectric smoothing is {dielectricsmoothing}."
)

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -45,7 +45,7 @@ class UserObjectGeometry:
return f"{self.hash}: {s[:-1]}"
def create(self, grid, uip):
def build(self, grid, uip):
"""Creates object and adds it to the grid."""
pass
@@ -64,6 +64,27 @@ class UserObjectGeometry:
return ""
def check_averaging(averaging):
"""Check and set material averaging value.
Args:
averaging: string for input value from hash command - should be 'y'
or 'n'.
Returns:
averaging: boolean for geometry object material averaging.
"""
if averaging == "y":
averaging = True
elif averaging == "n":
averaging = False
else:
logger.exception("Averaging should be either y or n")
return averaging
def rotate_point(p, axis, angle, origin=(0, 0, 0)):
"""Rotates a point.

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -22,7 +22,7 @@ import numpy as np
from ..cython.geometry_primitives import build_cone
from ..materials import Material
from .cmds_geometry import UserObjectGeometry
from .cmds_geometry import UserObjectGeometry, check_averaging
logger = logging.getLogger(__name__)
@@ -48,23 +48,26 @@ class Cone(UserObjectGeometry):
super().__init__(**kwargs)
self.hash = "#cone"
def create(self, grid, uip):
def build(self, grid, uip):
try:
p1 = self.kwargs["p1"]
p2 = self.kwargs["p2"]
r1 = self.kwargs["r1"]
r2 = self.kwargs["r2"]
except KeyError:
logger.exception(f"{self.__str__()} please specify two points and two radii")
logger.exception(
f"{self.__str__()} please specify two points and two radii"
)
raise
# Check averaging
try:
# Try user-specified averaging
averagecylinder = self.kwargs["averaging"]
averagecone = self.kwargs["averaging"]
averagecone = check_averaging(averagecone)
except KeyError:
# Otherwise go with the grid default
averagecylinder = grid.averagevolumeobjects
averagecone = grid.averagevolumeobjects
# Check materials have been specified
# Isotropic case
@@ -85,11 +88,15 @@ class Cone(UserObjectGeometry):
x2, y2, z2 = uip.round_to_grid(p2)
if r1 < 0:
logger.exception(f"{self.__str__()} the radius of the first face " + f"{r1:g} should be a positive value.")
logger.exception(
f"{self.__str__()} the radius of the first face {r1:g} should be a positive value."
)
raise ValueError
if r2 < 0:
logger.exception(f"{self.__str__()} the radius of the second face " + f"{r2:g} should be a positive value.")
logger.exception(
f"{self.__str__()} the radius of the second face {r2:g} should be a positive value."
)
raise ValueError
if r1 == 0 and r2 == 0:
@@ -106,7 +113,7 @@ class Cone(UserObjectGeometry):
# Isotropic case
if len(materials) == 1:
averaging = materials[0].averagable and averagecylinder
averaging = materials[0].averagable and averagecone
numID = numIDx = numIDy = numIDz = materials[0].numID
# Uniaxial anisotropic case
@@ -124,10 +131,18 @@ class Cone(UserObjectGeometry):
m = Material(numID, requiredID)
m.type = "dielectric-smoothed"
# Create dielectric-smoothed constituents for material
m.er = np.mean((materials[0].er, materials[1].er, materials[2].er), axis=0)
m.se = np.mean((materials[0].se, materials[1].se, materials[2].se), axis=0)
m.mr = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0)
m.sm = np.mean((materials[0].sm, materials[1].sm, materials[2].sm), axis=0)
m.er = np.mean(
(materials[0].er, materials[1].er, materials[2].er), axis=0
)
m.se = np.mean(
(materials[0].se, materials[1].se, materials[2].se), axis=0
)
m.mr = np.mean(
(materials[0].mr, materials[1].mr, materials[2].mr), axis=0
)
m.sm = np.mean(
(materials[0].sm, materials[1].sm, materials[2].sm), axis=0
)
# Append the new material object to the materials list
grid.materials.append(m)
@@ -158,7 +173,7 @@ class Cone(UserObjectGeometry):
dielectricsmoothing = "on" if averaging else "off"
logger.info(
f"{self.grid_name(grid)}Cone with face centres {p3[0]:g}m, "
+ f"{p3[1]:g}m, {p3[2]:g}m and {p4[0]:g}m, {p4[1]:g}m, {p4[2]:g}m, "
+ f"with radii {r1:g}m and {r2:g}, of material(s) {', '.join(materialsrequested)} "
+ f"created, dielectric smoothing is {dielectricsmoothing}."
f"{p3[1]:g}m, {p3[2]:g}m and {p4[0]:g}m, {p4[1]:g}m, {p4[2]:g}m, "
f"with radii {r1:g}m and {r2:g}, of material(s) {', '.join(materialsrequested)} "
f"created, dielectric smoothing is {dielectricsmoothing}."
)

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -22,7 +22,7 @@ import numpy as np
from ..cython.geometry_primitives import build_cylinder
from ..materials import Material
from .cmds_geometry import UserObjectGeometry
from .cmds_geometry import UserObjectGeometry, check_averaging
logger = logging.getLogger(__name__)
@@ -46,7 +46,7 @@ class Cylinder(UserObjectGeometry):
super().__init__(**kwargs)
self.hash = "#cylinder"
def create(self, grid, uip):
def build(self, grid, uip):
try:
p1 = self.kwargs["p1"]
p2 = self.kwargs["p2"]
@@ -59,6 +59,7 @@ class Cylinder(UserObjectGeometry):
try:
# Try user-specified averaging
averagecylinder = self.kwargs["averaging"]
averagecylinder = check_averaging(averagecylinder)
except KeyError:
# Otherwise go with the grid default
averagecylinder = grid.averagevolumeobjects
@@ -82,7 +83,9 @@ class Cylinder(UserObjectGeometry):
x2, y2, z2 = uip.round_to_grid(p2)
if r <= 0:
logger.exception(f"{self.__str__()} the radius {r:g} should be a positive value.")
logger.exception(
f"{self.__str__()} the radius {r:g} should be a positive value."
)
raise ValueError
# Look up requested materials in existing list of material instances
@@ -113,10 +116,18 @@ class Cylinder(UserObjectGeometry):
m = Material(numID, requiredID)
m.type = "dielectric-smoothed"
# Create dielectric-smoothed constituents for material
m.er = np.mean((materials[0].er, materials[1].er, materials[2].er), axis=0)
m.se = np.mean((materials[0].se, materials[1].se, materials[2].se), axis=0)
m.mr = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0)
m.sm = np.mean((materials[0].sm, materials[1].sm, materials[2].sm), axis=0)
m.er = np.mean(
(materials[0].er, materials[1].er, materials[2].er), axis=0
)
m.se = np.mean(
(materials[0].se, materials[1].se, materials[2].se), axis=0
)
m.mr = np.mean(
(materials[0].mr, materials[1].mr, materials[2].mr), axis=0
)
m.sm = np.mean(
(materials[0].sm, materials[1].sm, materials[2].sm), axis=0
)
# Append the new material object to the materials list
grid.materials.append(m)
@@ -146,7 +157,7 @@ class Cylinder(UserObjectGeometry):
dielectricsmoothing = "on" if averaging else "off"
logger.info(
f"{self.grid_name(grid)}Cylinder with face centres {p3[0]:g}m, "
+ f"{p3[1]:g}m, {p3[2]:g}m and {p4[0]:g}m, {p4[1]:g}m, {p4[2]:g}m, "
+ f"with radius {r:g}m, of material(s) {', '.join(materialsrequested)} "
+ f"created, dielectric smoothing is {dielectricsmoothing}."
f"{p3[1]:g}m, {p3[2]:g}m and {p4[0]:g}m, {p4[1]:g}m, {p4[2]:g}m, "
f"with radius {r:g}m, of material(s) {', '.join(materialsrequested)} "
f"created, dielectric smoothing is {dielectricsmoothing}."
)

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -22,7 +22,7 @@ import numpy as np
from ..cython.geometry_primitives import build_cylindrical_sector
from ..materials import Material
from .cmds_geometry import UserObjectGeometry
from .cmds_geometry import UserObjectGeometry, check_averaging
logger = logging.getLogger(__name__)
@@ -55,7 +55,7 @@ class CylindricalSector(UserObjectGeometry):
super().__init__(**kwargs)
self.hash = "#cylindrical_sector"
def create(self, grid, uip):
def build(self, grid, uip):
try:
normal = self.kwargs["normal"].lower()
ctr1 = self.kwargs["ctr1"]
@@ -74,6 +74,7 @@ class CylindricalSector(UserObjectGeometry):
try:
# Try user-specified averaging
averagecylindricalsector = self.kwargs["averaging"]
averagecylindricalsector = check_averaging(averagecylindricalsector)
except KeyError:
# Otherwise go with the grid default
averagecylindricalsector = grid.averagevolumeobjects
@@ -94,16 +95,22 @@ class CylindricalSector(UserObjectGeometry):
sectorangle = 2 * np.pi * (end / 360)
if normal not in ["x", "y", "z"]:
logger.exception(f"{self.__str__()} the normal direction must be either " + f"x, y or z.")
logger.exception(
f"{self.__str__()} the normal direction must be either x, y or z."
)
raise ValueError
if r <= 0:
logger.exception(f"{self.__str__()} the radius {r:g} should be a positive value.")
logger.exception(
f"{self.__str__()} the radius {r:g} should be a positive value."
)
if sectorstartangle < 0 or sectorangle <= 0:
logger.exception(f"{self.__str__()} the starting angle and sector angle should be " + f"a positive values.")
logger.exception(
f"{self.__str__()} the starting angle and sector angle should be a positive values."
)
raise ValueError
if sectorstartangle >= 2 * np.pi or sectorangle >= 2 * np.pi:
logger.exception(
f"{self.__str__()} the starting angle and sector angle must be " + f"less than 360 degrees."
f"{self.__str__()} the starting angle and sector angle must be less than 360 degrees."
)
raise ValueError
@@ -135,10 +142,18 @@ class CylindricalSector(UserObjectGeometry):
m = Material(numID, requiredID)
m.type = "dielectric-smoothed"
# Create dielectric-smoothed constituents for material
m.er = np.mean((materials[0].er, materials[1].er, materials[2].er), axis=0)
m.se = np.mean((materials[0].se, materials[1].se, materials[2].se), axis=0)
m.mr = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0)
m.sm = np.mean((materials[0].sm, materials[1].sm, materials[2].sm), axis=0)
m.er = np.mean(
(materials[0].er, materials[1].er, materials[2].er), axis=0
)
m.se = np.mean(
(materials[0].se, materials[1].se, materials[2].se), axis=0
)
m.mr = np.mean(
(materials[0].mr, materials[1].mr, materials[2].mr), axis=0
)
m.sm = np.mean(
(materials[0].sm, materials[1].sm, materials[2].sm), axis=0
)
# Append the new material object to the materials list
grid.materials.append(m)
@@ -195,18 +210,18 @@ class CylindricalSector(UserObjectGeometry):
dielectricsmoothing = "on" if averaging else "off"
logger.info(
f"{self.grid_name(grid)}Cylindrical sector with centre "
+ f"{ctr1:g}m, {ctr2:g}m, radius {r:g}m, starting angle "
+ f"{(sectorstartangle / (2 * np.pi)) * 360:.1f} degrees, "
+ f"sector angle {(sectorangle / (2 * np.pi)) * 360:.1f} degrees, "
+ f"thickness {thickness:g}m, of material(s) {', '.join(materialsrequested)} "
+ f"created, dielectric smoothing is {dielectricsmoothing}."
f"{ctr1:g}m, {ctr2:g}m, radius {r:g}m, starting angle "
f"{(sectorstartangle / (2 * np.pi)) * 360:.1f} degrees, "
f"sector angle {(sectorangle / (2 * np.pi)) * 360:.1f} degrees, "
f"thickness {thickness:g}m, of material(s) {', '.join(materialsrequested)} "
f"created, dielectric smoothing is {dielectricsmoothing}."
)
else:
logger.info(
f"{self.grid_name(grid)}Cylindrical sector with centre "
+ f"{ctr1:g}m, {ctr2:g}m, radius {r:g}m, starting angle "
+ f"{(sectorstartangle / (2 * np.pi)) * 360:.1f} degrees, "
+ f"sector angle {(sectorangle / (2 * np.pi)) * 360:.1f} "
+ f"degrees, of material(s) {', '.join(materialsrequested)} "
+ f"created."
f"{ctr1:g}m, {ctr2:g}m, radius {r:g}m, starting angle "
f"{(sectorstartangle / (2 * np.pi)) * 360:.1f} degrees, "
f"sector angle {(sectorangle / (2 * np.pi)) * 360:.1f} "
f"degrees, of material(s) {', '.join(materialsrequested)} "
f"created."
)

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -54,7 +54,7 @@ class Edge(UserObjectGeometry):
self.kwargs["p1"] = tuple(rot_pts[0, :])
self.kwargs["p2"] = tuple(rot_pts[1, :])
def create(self, grid, uip):
def build(self, grid, uip):
"""Creates edge and adds it to the grid."""
try:
p1 = self.kwargs["p1"]
@@ -91,18 +91,24 @@ class Edge(UserObjectGeometry):
raise ValueError
elif xs != xf:
for i in range(xs, xf):
build_edge_x(i, ys, zs, material.numID, grid.rigidE, grid.rigidH, grid.ID)
build_edge_x(
i, ys, zs, material.numID, grid.rigidE, grid.rigidH, grid.ID
)
elif ys != yf:
for j in range(ys, yf):
build_edge_y(xs, j, zs, material.numID, grid.rigidE, grid.rigidH, grid.ID)
build_edge_y(
xs, j, zs, material.numID, grid.rigidE, grid.rigidH, grid.ID
)
elif zs != zf:
for k in range(zs, zf):
build_edge_z(xs, ys, k, material.numID, grid.rigidE, grid.rigidH, grid.ID)
build_edge_z(
xs, ys, k, material.numID, grid.rigidE, grid.rigidH, grid.ID
)
logger.info(
f"{self.grid_name(grid)}Edge from {p3[0]:g}m, {p3[1]:g}m, "
+ f"{p3[2]:g}m, to {p4[0]:g}m, {p4[1]:g}m, {p4[2]:g}m of "
+ f"material {material_id} created."
f"{p3[2]:g}m, to {p4[0]:g}m, {p4[1]:g}m, {p4[2]:g}m of "
f"material {material_id} created."
)

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -22,7 +22,7 @@ import numpy as np
from ..cython.geometry_primitives import build_ellipsoid
from ..materials import Material
from .cmds_geometry import UserObjectGeometry
from .cmds_geometry import UserObjectGeometry, check_averaging
logger = logging.getLogger(__name__)
@@ -45,7 +45,7 @@ class Ellipsoid(UserObjectGeometry):
super().__init__(**kwargs)
self.hash = "#ellipsoid"
def create(self, grid, uip):
def build(self, grid, uip):
try:
p1 = self.kwargs["p1"]
xr = self.kwargs["xr"]
@@ -53,13 +53,16 @@ class Ellipsoid(UserObjectGeometry):
zr = self.kwargs["zr"]
except KeyError:
logger.exception(f"{self.__str__()} please specify a point and " + f"the three semiaxes.")
logger.exception(
f"{self.__str__()} please specify a point and the three semiaxes."
)
raise
# Check averaging
try:
# Try user-specified averaging
averageellipsoid = self.kwargs["averaging"]
averageellipsoid = check_averaging(averageellipsoid)
except KeyError:
# Otherwise go with the grid default
averageellipsoid = grid.averagevolumeobjects
@@ -108,10 +111,18 @@ class Ellipsoid(UserObjectGeometry):
m = Material(numID, requiredID)
m.type = "dielectric-smoothed"
# Create dielectric-smoothed constituents for material
m.er = np.mean((materials[0].er, materials[1].er, materials[2].er), axis=0)
m.se = np.mean((materials[0].se, materials[1].se, materials[2].se), axis=0)
m.mr = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0)
m.sm = np.mean((materials[0].sm, materials[1].sm, materials[2].sm), axis=0)
m.er = np.mean(
(materials[0].er, materials[1].er, materials[2].er), axis=0
)
m.se = np.mean(
(materials[0].se, materials[1].se, materials[2].se), axis=0
)
m.mr = np.mean(
(materials[0].mr, materials[1].mr, materials[2].mr), axis=0
)
m.sm = np.mean(
(materials[0].sm, materials[1].sm, materials[2].sm), axis=0
)
# Append the new material object to the materials list
grid.materials.append(m)
@@ -140,8 +151,8 @@ class Ellipsoid(UserObjectGeometry):
dielectricsmoothing = "on" if averaging else "off"
logger.info(
f"{self.grid_name(grid)}Ellipsoid with centre {p2[0]:g}m, "
+ f"{p2[1]:g}m, {p2[2]:g}m, x-semiaxis {xr:g}m, "
+ f"y-semiaxis {yr:g}m and z-semiaxis {zr:g}m of material(s) "
+ f"{', '.join(materialsrequested)} created, dielectric "
+ f"smoothing is {dielectricsmoothing}."
f"{p2[1]:g}m, {p2[2]:g}m, x-semiaxis {xr:g}m, "
f"y-semiaxis {yr:g}m and z-semiaxis {zr:g}m of material(s) "
f"{', '.join(materialsrequested)} created, dielectric "
f"smoothing is {dielectricsmoothing}."
)

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -20,9 +20,15 @@ import logging
import numpy as np
from ..fractals import FractalVolume
from ..materials import ListMaterial
from .cmds_geometry import UserObjectGeometry, rotate_2point_object
import gprMax.config as config
from gprMax.cmds_geometry.cmds_geometry import UserObjectGeometry, rotate_2point_object
from gprMax.fractals import FractalVolume
from gprMax.materials import ListMaterial
from ..cython.geometry_primitives import (
build_voxels_from_array,
build_voxels_from_array_mask,
)
logger = logging.getLogger(__name__)
@@ -53,6 +59,7 @@ class FractalBox(UserObjectGeometry):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.do_pre_build = True
self.hash = "#fractal_box"
def rotate(self, axis, angle, origin=None):
@@ -69,7 +76,7 @@ class FractalBox(UserObjectGeometry):
self.kwargs["p1"] = tuple(rot_pts[0, :])
self.kwargs["p2"] = tuple(rot_pts[1, :])
def create(self, grid, uip):
def pre_build(self, grid, uip):
try:
p1 = self.kwargs["p1"]
p2 = self.kwargs["p2"]
@@ -83,12 +90,12 @@ class FractalBox(UserObjectGeometry):
raise
try:
seed = self.kwargs["seed"]
seed = int(self.kwargs["seed"])
except KeyError:
logger.warning(
f"{self.__str__()} no value for seed detected. This "
+ "means you will get a different fractal distribution "
+ "every time the model runs."
"means you will get a different fractal distribution "
"every time the model runs."
)
seed = None
@@ -112,74 +119,705 @@ class FractalBox(UserObjectGeometry):
xf, yf, zf = p2
if frac_dim < 0:
logger.exception(f"{self.__str__()} requires a positive value for the " + "fractal dimension")
logger.exception(
f"{self.__str__()} requires a positive value for the fractal dimension"
)
raise ValueError
if weighting[0] < 0:
logger.exception(
f"{self.__str__()} requires a positive value for the " + "fractal weighting in the x direction"
f"{self.__str__()} requires a positive value for the fractal weighting in the x direction"
)
raise ValueError
if weighting[1] < 0:
logger.exception(
f"{self.__str__()} requires a positive value for the " + "fractal weighting in the y direction"
f"{self.__str__()} requires a positive value for the fractal weighting in the y direction"
)
raise ValueError
if weighting[2] < 0:
logger.exception(
f"{self.__str__()} requires a positive value for the " + "fractal weighting in the z direction"
f"{self.__str__()} requires a positive value for the fractal weighting in the z direction"
)
if n_materials < 0:
logger.exception(f"{self.__str__()} requires a positive value for the " + "number of bins")
logger.exception(
f"{self.__str__()} requires a positive value for the number of bins"
)
raise ValueError
# Find materials to use to build fractal volume, either from mixing
# models or normal materials.
mixingmodel = next((x for x in grid.mixingmodels if x.ID == mixing_model_id), None)
mixingmodel = next(
(x for x in grid.mixingmodels if x.ID == mixing_model_id), None
)
material = next((x for x in grid.materials if x.ID == mixing_model_id), None)
nbins = n_materials
if mixingmodel:
if nbins == 1:
logger.exception(
f"{self.__str__()} must be used with more than " + "one material from the mixing model."
f"{self.__str__()} must be used with more than one material from the mixing model."
)
raise ValueError
if isinstance(mixingmodel, ListMaterial) and nbins > len(mixingmodel.mat):
logger.exception(
f"{self.__str__()} too many materials/bins "
+ "requested compared to materials in "
+ "mixing model."
"requested compared to materials in "
"mixing model."
)
raise ValueError
# Create materials from mixing model as number of bins now known
# from fractal_box command.
mixingmodel.calculate_properties(nbins, grid)
elif not material:
logger.exception(f"{self.__str__()} mixing model or material with " + "ID {mixing_model_id} does not exist")
logger.exception(
f"{self.__str__()} mixing model or material with "
+ "ID {mixing_model_id} does not exist"
)
raise ValueError
volume = FractalVolume(xs, xf, ys, yf, zs, zf, frac_dim)
volume.ID = ID
volume.operatingonID = mixing_model_id
volume.nbins = nbins
if seed is not None:
volume.seed = int(seed)
else:
volume.seed = seed
volume.weighting = weighting
volume.averaging = averagefractalbox
volume.mixingmodel = mixingmodel
self.volume = FractalVolume(xs, xf, ys, yf, zs, zf, frac_dim, seed)
self.volume.ID = ID
self.volume.operatingonID = mixing_model_id
self.volume.nbins = nbins
self.volume.weighting = weighting
self.volume.averaging = averagefractalbox
self.volume.mixingmodel = mixingmodel
dielectricsmoothing = "on" if volume.averaging else "off"
dielectricsmoothing = "on" if self.volume.averaging else "off"
logger.info(
f"{self.grid_name(grid)}Fractal box {volume.ID} from "
+ f"{p3[0]:g}m, {p3[1]:g}m, {p3[2]:g}m, to {p4[0]:g}m, "
+ f"{p4[1]:g}m, {p4[2]:g}m with {volume.operatingonID}, "
+ f"fractal dimension {volume.dimension:g}, fractal weightings "
+ f"{volume.weighting[0]:g}, {volume.weighting[1]:g}, "
+ f"{volume.weighting[2]:g}, fractal seeding {volume.seed}, "
+ f"with {volume.nbins} material(s) created, dielectric smoothing "
+ f"is {dielectricsmoothing}."
f"{self.grid_name(grid)}Fractal box {self.volume.ID} from "
f"{p3[0]:g}m, {p3[1]:g}m, {p3[2]:g}m, to {p4[0]:g}m, "
f"{p4[1]:g}m, {p4[2]:g}m with {self.volume.operatingonID}, "
f"fractal dimension {self.volume.dimension:g}, fractal weightings "
f"{self.volume.weighting[0]:g}, {self.volume.weighting[1]:g}, "
f"{self.volume.weighting[2]:g}, fractal seeding {self.volume.seed}, "
f"with {self.volume.nbins} material(s) created, dielectric smoothing "
f"is {dielectricsmoothing}."
)
grid.fractalvolumes.append(self.volume)
grid.fractalvolumes.append(volume)
def build(self, grid, uip):
if self.do_pre_build:
self.pre_build(grid, uip)
self.do_pre_build = False
else:
if self.volume.fractalsurfaces:
self.volume.originalxs = self.volume.xs
self.volume.originalxf = self.volume.xf
self.volume.originalys = self.volume.ys
self.volume.originalyf = self.volume.yf
self.volume.originalzs = self.volume.zs
self.volume.originalzf = self.volume.zf
# Extend the volume to accomodate any rough surfaces, grass,
# or roots
for surface in self.volume.fractalsurfaces:
if surface.surfaceID == "xminus":
if surface.fractalrange[0] < self.volume.xs:
self.volume.nx += self.volume.xs - surface.fractalrange[0]
self.volume.xs = surface.fractalrange[0]
elif surface.surfaceID == "xplus":
if surface.fractalrange[1] > self.volume.xf:
self.volume.nx += surface.fractalrange[1] - self.volume.xf
self.volume.xf = surface.fractalrange[1]
elif surface.surfaceID == "yminus":
if surface.fractalrange[0] < self.volume.ys:
self.volume.ny += self.volume.ys - surface.fractalrange[0]
self.volume.ys = surface.fractalrange[0]
elif surface.surfaceID == "yplus":
if surface.fractalrange[1] > self.volume.yf:
self.volume.ny += surface.fractalrange[1] - self.volume.yf
self.volume.yf = surface.fractalrange[1]
elif surface.surfaceID == "zminus":
if surface.fractalrange[0] < self.volume.zs:
self.volume.nz += self.volume.zs - surface.fractalrange[0]
self.volume.zs = surface.fractalrange[0]
elif surface.surfaceID == "zplus":
if surface.fractalrange[1] > self.volume.zf:
self.volume.nz += surface.fractalrange[1] - self.volume.zf
self.volume.zf = surface.fractalrange[1]
# If there is only 1 bin then a normal material is being used,
# otherwise a mixing model
if self.volume.nbins == 1:
self.volume.fractalvolume = np.ones(
(self.volume.nx, self.volume.ny, self.volume.nz),
dtype=config.sim_config.dtypes["float_or_double"],
)
materialnumID = next(
x.numID
for x in grid.materials
if x.ID == self.volume.operatingonID
)
self.volume.fractalvolume *= materialnumID
else:
self.volume.generate_fractal_volume()
for i in range(0, self.volume.nx):
for j in range(0, self.volume.ny):
for k in range(0, self.volume.nz):
numberinbin = self.volume.fractalvolume[i, j, k]
self.volume.fractalvolume[i, j, k] = (
self.volume.mixingmodel.matID[int(numberinbin)]
)
self.volume.generate_volume_mask()
# Apply any rough surfaces and add any surface water to the
# 3D mask array
# TODO: Allow extract of rough surface profile (to print/file?)
for surface in self.volume.fractalsurfaces:
if surface.surfaceID == "xminus":
for i in range(
surface.fractalrange[0], surface.fractalrange[1]
):
for j in range(surface.ys, surface.yf):
for k in range(surface.zs, surface.zf):
if (
i
> surface.fractalsurface[
j - surface.ys, k - surface.zs
]
):
self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
] = 1
elif (
surface.filldepth > 0 and i > surface.filldepth
):
self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
] = 2
else:
self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
] = 0
elif surface.surfaceID == "xplus":
if not surface.ID:
for i in range(
surface.fractalrange[0], surface.fractalrange[1]
):
for j in range(surface.ys, surface.yf):
for k in range(surface.zs, surface.zf):
if (
i
< surface.fractalsurface[
j - surface.ys, k - surface.zs
]
):
self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
] = 1
elif (
surface.filldepth > 0
and i < surface.filldepth
):
self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
] = 2
else:
self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
] = 0
elif surface.ID == "grass":
g = surface.grass[0]
# Build the blades of the grass
blade = 0
for j in range(surface.ys, surface.yf):
for k in range(surface.zs, surface.zf):
if (
surface.fractalsurface[
j - surface.ys, k - surface.zs
]
> 0
):
height = 0
for i in range(
self.volume.xs, surface.fractalrange[1]
):
if (
i
< surface.fractalsurface[
j - surface.ys, k - surface.zs
]
and self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
]
!= 1
):
y, z = g.calculate_blade_geometry(
blade, height
)
# Add y, z coordinates to existing location
yy = int(j - self.volume.ys + y)
zz = int(k - self.volume.zs + z)
# If these coordinates are outwith fractal volume stop building the blade,
# otherwise set the mask for grass.
if (
yy < 0
or yy >= self.volume.mask.shape[1]
or zz < 0
or zz >= self.volume.mask.shape[2]
):
break
else:
self.volume.mask[
i - self.volume.xs, yy, zz
] = 3
height += 1
blade += 1
# Build the roots of the grass
root = 0
for j in range(surface.ys, surface.yf):
for k in range(surface.zs, surface.zf):
if (
surface.fractalsurface[
j - surface.ys, k - surface.zs
]
> 0
):
depth = 0
i = self.volume.xf - 1
while i > self.volume.xs:
if (
i
> self.volume.originalxf
- (
surface.fractalsurface[
j - surface.ys, k - surface.zs
]
- self.volume.originalxf
)
and self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
]
== 1
):
y, z = g.calculate_root_geometry(
root, depth
)
# Add y, z coordinates to existing location
yy = int(j - self.volume.ys + y)
zz = int(k - self.volume.zs + z)
# If these coordinates are outwith the fractal volume stop building the root,
# otherwise set the mask for grass.
if (
yy < 0
or yy >= self.volume.mask.shape[1]
or zz < 0
or zz >= self.volume.mask.shape[2]
):
break
else:
self.volume.mask[
i - self.volume.xs, yy, zz
] = 3
depth += 1
i -= 1
root += 1
elif surface.surfaceID == "yminus":
for i in range(surface.xs, surface.xf):
for j in range(
surface.fractalrange[0], surface.fractalrange[1]
):
for k in range(surface.zs, surface.zf):
if (
j
> surface.fractalsurface[
i - surface.xs, k - surface.zs
]
):
self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
] = 1
elif (
surface.filldepth > 0 and j > surface.filldepth
):
self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
] = 2
else:
self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
] = 0
elif surface.surfaceID == "yplus":
if not surface.ID:
for i in range(surface.xs, surface.xf):
for j in range(
surface.fractalrange[0], surface.fractalrange[1]
):
for k in range(surface.zs, surface.zf):
if (
j
< surface.fractalsurface[
i - surface.xs, k - surface.zs
]
):
self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
] = 1
elif (
surface.filldepth > 0
and j < surface.filldepth
):
self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
] = 2
else:
self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
] = 0
elif surface.ID == "grass":
g = surface.grass[0]
# Build the blades of the grass
blade = 0
for i in range(surface.xs, surface.xf):
for k in range(surface.zs, surface.zf):
if (
surface.fractalsurface[
i - surface.xs, k - surface.zs
]
> 0
):
height = 0
for j in range(
self.volume.ys, surface.fractalrange[1]
):
if (
j
< surface.fractalsurface[
i - surface.xs, k - surface.zs
]
and self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
]
!= 1
):
x, z = g.calculate_blade_geometry(
blade, height
)
# Add x, z coordinates to existing location
xx = int(i - self.volume.xs + x)
zz = int(k - self.volume.zs + z)
# If these coordinates are outwith fractal volume stop building the blade,
# otherwise set the mask for grass.
if (
xx < 0
or xx >= self.volume.mask.shape[0]
or zz < 0
or zz >= self.volume.mask.shape[2]
):
break
else:
self.volume.mask[
xx, j - self.volume.ys, zz
] = 3
height += 1
blade += 1
# Build the roots of the grass
root = 0
for i in range(surface.xs, surface.xf):
for k in range(surface.zs, surface.zf):
if (
surface.fractalsurface[
i - surface.xs, k - surface.zs
]
> 0
):
depth = 0
j = self.volume.yf - 1
while j > self.volume.ys:
if (
j
> self.volume.originalyf
- (
surface.fractalsurface[
i - surface.xs, k - surface.zs
]
- self.volume.originalyf
)
and self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
]
== 1
):
x, z = g.calculate_root_geometry(
root, depth
)
# Add x, z coordinates to existing location
xx = int(i - self.volume.xs + x)
zz = int(k - self.volume.zs + z)
# If these coordinates are outwith the fractal volume stop building the root,
# otherwise set the mask for grass.
if (
xx < 0
or xx >= self.volume.mask.shape[0]
or zz < 0
or zz >= self.volume.mask.shape[2]
):
break
else:
self.volume.mask[
xx, j - self.volume.ys, zz
] = 3
depth += 1
j -= 1
root += 1
elif surface.surfaceID == "zminus":
for i in range(surface.xs, surface.xf):
for j in range(surface.ys, surface.yf):
for k in range(
surface.fractalrange[0], surface.fractalrange[1]
):
if (
k
> surface.fractalsurface[
i - surface.xs, j - surface.ys
]
):
self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
] = 1
elif (
surface.filldepth > 0 and k > surface.filldepth
):
self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
] = 2
else:
self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
] = 0
elif surface.surfaceID == "zplus":
if not surface.ID:
for i in range(surface.xs, surface.xf):
for j in range(surface.ys, surface.yf):
for k in range(
surface.fractalrange[0], surface.fractalrange[1]
):
if (
k
< surface.fractalsurface[
i - surface.xs, j - surface.ys
]
):
self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
] = 1
elif (
surface.filldepth > 0
and k < surface.filldepth
):
self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
] = 2
else:
self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
] = 0
elif surface.ID == "grass":
g = surface.grass[0]
# Build the blades of the grass
blade = 0
for i in range(surface.xs, surface.xf):
for j in range(surface.ys, surface.yf):
if (
surface.fractalsurface[
i - surface.xs, j - surface.ys
]
> 0
):
height = 0
for k in range(
self.volume.zs, surface.fractalrange[1]
):
if (
k
< surface.fractalsurface[
i - surface.xs, j - surface.ys
]
and self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
]
!= 1
):
x, y = g.calculate_blade_geometry(
blade, height
)
# Add x, y coordinates to existing location
xx = int(i - self.volume.xs + x)
yy = int(j - self.volume.ys + y)
# If these coordinates are outwith the fractal volume stop building the blade,
# otherwise set the mask for grass.
if (
xx < 0
or xx >= self.volume.mask.shape[0]
or yy < 0
or yy >= self.volume.mask.shape[1]
):
break
else:
self.volume.mask[
xx, yy, k - self.volume.zs
] = 3
height += 1
blade += 1
# Build the roots of the grass
root = 0
for i in range(surface.xs, surface.xf):
for j in range(surface.ys, surface.yf):
if (
surface.fractalsurface[
i - surface.xs, j - surface.ys
]
> 0
):
depth = 0
k = self.volume.zf - 1
while k > self.volume.zs:
if (
k
> self.volume.originalzf
- (
surface.fractalsurface[
i - surface.xs, j - surface.ys
]
- self.volume.originalzf
)
and self.volume.mask[
i - self.volume.xs,
j - self.volume.ys,
k - self.volume.zs,
]
== 1
):
x, y = g.calculate_root_geometry(
root, depth
)
# Add x, y coordinates to existing location
xx = int(i - self.volume.xs + x)
yy = int(j - self.volume.ys + y)
# If these coordinates are outwith the fractal volume stop building the root,
# otherwise set the mask for grass.
if (
xx < 0
or xx >= self.volume.mask.shape[0]
or yy < 0
or yy >= self.volume.mask.shape[1]
):
break
else:
self.volume.mask[
xx, yy, k - self.volume.zs
] = 3
depth += 1
k -= 1
root += 1
# Build voxels from any true values of the 3D mask array
waternumID = next(
(x.numID for x in grid.materials if x.ID == "water"), 0
)
grassnumID = next(
(x.numID for x in grid.materials if x.ID == "grass"), 0
)
data = self.volume.fractalvolume.astype("int16", order="C")
mask = self.volume.mask.copy(order="C")
build_voxels_from_array_mask(
self.volume.xs,
self.volume.ys,
self.volume.zs,
waternumID,
grassnumID,
self.volume.averaging,
mask,
data,
grid.solid,
grid.rigidE,
grid.rigidH,
grid.ID,
)
else:
if self.volume.nbins == 1:
logger.exception(
f"{self.__str__()} is being used with a "
"single material and no modifications, "
"therefore please use a #box command instead."
)
raise ValueError
else:
self.volume.generate_fractal_volume()
for i in range(0, self.volume.nx):
for j in range(0, self.volume.ny):
for k in range(0, self.volume.nz):
numberinbin = self.volume.fractalvolume[i, j, k]
self.volume.fractalvolume[i, j, k] = (
self.volume.mixingmodel.matID[int(numberinbin)]
)
data = self.volume.fractalvolume.astype("int16", order="C")
build_voxels_from_array(
self.volume.xs,
self.volume.ys,
self.volume.zs,
0,
self.volume.averaging,
data,
grid.solid,
grid.rigidE,
grid.rigidH,
grid.ID,
)

查看文件

@@ -1,416 +0,0 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
#
# gprMax is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# gprMax is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with gprMax. If not, see <http://www.gnu.org/licenses/>.
import logging
import numpy as np
import gprMax.config as config
from ..cython.geometry_primitives import build_voxels_from_array, build_voxels_from_array_mask
from .cmds_geometry import UserObjectGeometry
logger = logging.getLogger(__name__)
class FractalBoxBuilder(UserObjectGeometry):
"""Internal class for fractal box modifications. This class should be used
internally only when surface modification have been made to a fractal box."""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.hash = "#fractal_box_modifications"
def create(self, grid, uip):
for volume in grid.fractalvolumes:
if volume.fractalsurfaces:
volume.originalxs = volume.xs
volume.originalxf = volume.xf
volume.originalys = volume.ys
volume.originalyf = volume.yf
volume.originalzs = volume.zs
volume.originalzf = volume.zf
# Extend the volume to accomodate any rough surfaces, grass,
# or roots
for surface in volume.fractalsurfaces:
if surface.surfaceID == "xminus":
if surface.fractalrange[0] < volume.xs:
volume.nx += volume.xs - surface.fractalrange[0]
volume.xs = surface.fractalrange[0]
elif surface.surfaceID == "xplus":
if surface.fractalrange[1] > volume.xf:
volume.nx += surface.fractalrange[1] - volume.xf
volume.xf = surface.fractalrange[1]
elif surface.surfaceID == "yminus":
if surface.fractalrange[0] < volume.ys:
volume.ny += volume.ys - surface.fractalrange[0]
volume.ys = surface.fractalrange[0]
elif surface.surfaceID == "yplus":
if surface.fractalrange[1] > volume.yf:
volume.ny += surface.fractalrange[1] - volume.yf
volume.yf = surface.fractalrange[1]
elif surface.surfaceID == "zminus":
if surface.fractalrange[0] < volume.zs:
volume.nz += volume.zs - surface.fractalrange[0]
volume.zs = surface.fractalrange[0]
elif surface.surfaceID == "zplus":
if surface.fractalrange[1] > volume.zf:
volume.nz += surface.fractalrange[1] - volume.zf
volume.zf = surface.fractalrange[1]
# If there is only 1 bin then a normal material is being used,
# otherwise a mixing model
if volume.nbins == 1:
volume.fractalvolume = np.ones(
(volume.nx, volume.ny, volume.nz), dtype=config.sim_config.dtypes["float_or_double"]
)
materialnumID = next(x.numID for x in grid.materials if x.ID == volume.operatingonID)
volume.fractalvolume *= materialnumID
else:
volume.generate_fractal_volume()
for i in range(0, volume.nx):
for j in range(0, volume.ny):
for k in range(0, volume.nz):
numberinbin = volume.fractalvolume[i, j, k]
volume.fractalvolume[i, j, k] = volume.mixingmodel.matID[int(numberinbin)]
volume.generate_volume_mask()
# Apply any rough surfaces and add any surface water to the
# 3D mask array
# TODO: Allow extract of rough surface profile (to print/file?)
for surface in volume.fractalsurfaces:
if surface.surfaceID == "xminus":
for i in range(surface.fractalrange[0], surface.fractalrange[1]):
for j in range(surface.ys, surface.yf):
for k in range(surface.zs, surface.zf):
if i > surface.fractalsurface[j - surface.ys, k - surface.zs]:
volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] = 1
elif surface.filldepth > 0 and i > surface.filldepth:
volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] = 2
else:
volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] = 0
elif surface.surfaceID == "xplus":
if not surface.ID:
for i in range(surface.fractalrange[0], surface.fractalrange[1]):
for j in range(surface.ys, surface.yf):
for k in range(surface.zs, surface.zf):
if i < surface.fractalsurface[j - surface.ys, k - surface.zs]:
volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] = 1
elif surface.filldepth > 0 and i < surface.filldepth:
volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] = 2
else:
volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] = 0
elif surface.ID == "grass":
g = surface.grass[0]
# Build the blades of the grass
blade = 0
for j in range(surface.ys, surface.yf):
for k in range(surface.zs, surface.zf):
if surface.fractalsurface[j - surface.ys, k - surface.zs] > 0:
height = 0
for i in range(volume.xs, surface.fractalrange[1]):
if (
i < surface.fractalsurface[j - surface.ys, k - surface.zs]
and volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] != 1
):
y, z = g.calculate_blade_geometry(blade, height)
# Add y, z coordinates to existing location
yy = int(j - volume.ys + y)
zz = int(k - volume.zs + z)
# If these coordinates are outwith fractal volume stop building the blade,
# otherwise set the mask for grass.
if (
yy < 0
or yy >= volume.mask.shape[1]
or zz < 0
or zz >= volume.mask.shape[2]
):
break
else:
volume.mask[i - volume.xs, yy, zz] = 3
height += 1
blade += 1
# Build the roots of the grass
root = 0
for j in range(surface.ys, surface.yf):
for k in range(surface.zs, surface.zf):
if surface.fractalsurface[j - surface.ys, k - surface.zs] > 0:
depth = 0
i = volume.xf - 1
while i > volume.xs:
if (
i
> volume.originalxf
- (
surface.fractalsurface[j - surface.ys, k - surface.zs]
- volume.originalxf
)
and volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] == 1
):
y, z = g.calculate_root_geometry(root, depth)
# Add y, z coordinates to existing location
yy = int(j - volume.ys + y)
zz = int(k - volume.zs + z)
# If these coordinates are outwith the fractal volume stop building the root,
# otherwise set the mask for grass.
if (
yy < 0
or yy >= volume.mask.shape[1]
or zz < 0
or zz >= volume.mask.shape[2]
):
break
else:
volume.mask[i - volume.xs, yy, zz] = 3
depth += 1
i -= 1
root += 1
elif surface.surfaceID == "yminus":
for i in range(surface.xs, surface.xf):
for j in range(surface.fractalrange[0], surface.fractalrange[1]):
for k in range(surface.zs, surface.zf):
if j > surface.fractalsurface[i - surface.xs, k - surface.zs]:
volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] = 1
elif surface.filldepth > 0 and j > surface.filldepth:
volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] = 2
else:
volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] = 0
elif surface.surfaceID == "yplus":
if not surface.ID:
for i in range(surface.xs, surface.xf):
for j in range(surface.fractalrange[0], surface.fractalrange[1]):
for k in range(surface.zs, surface.zf):
if j < surface.fractalsurface[i - surface.xs, k - surface.zs]:
volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] = 1
elif surface.filldepth > 0 and j < surface.filldepth:
volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] = 2
else:
volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] = 0
elif surface.ID == "grass":
g = surface.grass[0]
# Build the blades of the grass
blade = 0
for i in range(surface.xs, surface.xf):
for k in range(surface.zs, surface.zf):
if surface.fractalsurface[i - surface.xs, k - surface.zs] > 0:
height = 0
for j in range(volume.ys, surface.fractalrange[1]):
if (
j < surface.fractalsurface[i - surface.xs, k - surface.zs]
and volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] != 1
):
x, z = g.calculate_blade_geometry(blade, height)
# Add x, z coordinates to existing location
xx = int(i - volume.xs + x)
zz = int(k - volume.zs + z)
# If these coordinates are outwith fractal volume stop building the blade,
# otherwise set the mask for grass.
if (
xx < 0
or xx >= volume.mask.shape[0]
or zz < 0
or zz >= volume.mask.shape[2]
):
break
else:
volume.mask[xx, j - volume.ys, zz] = 3
height += 1
blade += 1
# Build the roots of the grass
root = 0
for i in range(surface.xs, surface.xf):
for k in range(surface.zs, surface.zf):
if surface.fractalsurface[i - surface.xs, k - surface.zs] > 0:
depth = 0
j = volume.yf - 1
while j > volume.ys:
if (
j
> volume.originalyf
- (
surface.fractalsurface[i - surface.xs, k - surface.zs]
- volume.originalyf
)
and volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] == 1
):
x, z = g.calculate_root_geometry(root, depth)
# Add x, z coordinates to existing location
xx = int(i - volume.xs + x)
zz = int(k - volume.zs + z)
# If these coordinates are outwith the fractal volume stop building the root,
# otherwise set the mask for grass.
if (
xx < 0
or xx >= volume.mask.shape[0]
or zz < 0
or zz >= volume.mask.shape[2]
):
break
else:
volume.mask[xx, j - volume.ys, zz] = 3
depth += 1
j -= 1
root += 1
elif surface.surfaceID == "zminus":
for i in range(surface.xs, surface.xf):
for j in range(surface.ys, surface.yf):
for k in range(surface.fractalrange[0], surface.fractalrange[1]):
if k > surface.fractalsurface[i - surface.xs, j - surface.ys]:
volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] = 1
elif surface.filldepth > 0 and k > surface.filldepth:
volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] = 2
else:
volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] = 0
elif surface.surfaceID == "zplus":
if not surface.ID:
for i in range(surface.xs, surface.xf):
for j in range(surface.ys, surface.yf):
for k in range(surface.fractalrange[0], surface.fractalrange[1]):
if k < surface.fractalsurface[i - surface.xs, j - surface.ys]:
volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] = 1
elif surface.filldepth > 0 and k < surface.filldepth:
volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] = 2
else:
volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] = 0
elif surface.ID == "grass":
g = surface.grass[0]
# Build the blades of the grass
blade = 0
for i in range(surface.xs, surface.xf):
for j in range(surface.ys, surface.yf):
if surface.fractalsurface[i - surface.xs, j - surface.ys] > 0:
height = 0
for k in range(volume.zs, surface.fractalrange[1]):
if (
k < surface.fractalsurface[i - surface.xs, j - surface.ys]
and volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] != 1
):
x, y = g.calculate_blade_geometry(blade, height)
# Add x, y coordinates to existing location
xx = int(i - volume.xs + x)
yy = int(j - volume.ys + y)
# If these coordinates are outwith the fractal volume stop building the blade,
# otherwise set the mask for grass.
if (
xx < 0
or xx >= volume.mask.shape[0]
or yy < 0
or yy >= volume.mask.shape[1]
):
break
else:
volume.mask[xx, yy, k - volume.zs] = 3
height += 1
blade += 1
# Build the roots of the grass
root = 0
for i in range(surface.xs, surface.xf):
for j in range(surface.ys, surface.yf):
if surface.fractalsurface[i - surface.xs, j - surface.ys] > 0:
depth = 0
k = volume.zf - 1
while k > volume.zs:
if (
k
> volume.originalzf
- (
surface.fractalsurface[i - surface.xs, j - surface.ys]
- volume.originalzf
)
and volume.mask[i - volume.xs, j - volume.ys, k - volume.zs] == 1
):
x, y = g.calculate_root_geometry(root, depth)
# Add x, y coordinates to existing location
xx = int(i - volume.xs + x)
yy = int(j - volume.ys + y)
# If these coordinates are outwith the fractal volume stop building the root,
# otherwise set the mask for grass.
if (
xx < 0
or xx >= volume.mask.shape[0]
or yy < 0
or yy >= volume.mask.shape[1]
):
break
else:
volume.mask[xx, yy, k - volume.zs] = 3
depth += 1
k -= 1
root += 1
# Build voxels from any true values of the 3D mask array
waternumID = next((x.numID for x in grid.materials if x.ID == "water"), 0)
grassnumID = next((x.numID for x in grid.materials if x.ID == "grass"), 0)
data = volume.fractalvolume.astype("int16", order="C")
mask = volume.mask.copy(order="C")
build_voxels_from_array_mask(
volume.xs,
volume.ys,
volume.zs,
config.get_model_config().ompthreads,
waternumID,
grassnumID,
volume.averaging,
mask,
data,
grid.solid,
grid.rigidE,
grid.rigidH,
grid.ID,
)
else:
if volume.nbins == 1:
logger.exception(
f"{self.__str__()} is being used with a "
+ "single material and no modifications, "
+ "therefore please use a #box command instead."
)
raise ValueError
else:
volume.generate_fractal_volume()
for i in range(0, volume.nx):
for j in range(0, volume.ny):
for k in range(0, volume.nz):
numberinbin = volume.fractalvolume[i, j, k]
volume.fractalvolume[i, j, k] = volume.mixingmodel.matID[int(numberinbin)]
data = volume.fractalvolume.astype("int16", order="C")
build_voxels_from_array(
volume.xs,
volume.ys,
volume.zs,
config.get_model_config().ompthreads,
0,
volume.averaging,
data,
grid.solid,
grid.rigidE,
grid.rigidH,
grid.ID,
)

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -39,7 +39,7 @@ class GeometryObjectsRead(UserObjectGeometry):
def rotate(self, axis, angle, origin=None):
pass
def create(self, grid, uip):
def build(self, grid, uip):
"""Creates the object and adds it to the grid."""
try:
p1 = self.kwargs["p1"]
@@ -71,7 +71,11 @@ class GeometryObjectsRead(UserObjectGeometry):
materials = [
line.rstrip() + "{" + matstr + "}\n"
for line in f
if (line.startswith("#") and not line.startswith("##") and line.rstrip("\n"))
if (
line.startswith("#")
and not line.startswith("##")
and line.rstrip("\n")
)
]
# Build scene
@@ -82,7 +86,7 @@ class GeometryObjectsRead(UserObjectGeometry):
scene.add(material_obj)
# Creates the internal simulation objects
scene.process_cmds(material_objs, grid, sort=False)
scene.process_cmds(material_objs, grid)
# Update material type
for material in grid.materials:
@@ -108,8 +112,8 @@ class GeometryObjectsRead(UserObjectGeometry):
):
logger.exception(
f"{self.__str__()} requires the spatial resolution "
+ "of the geometry objects file to match the spatial "
+ "resolution of the model"
"of the geometry objects file to match the spatial "
"resolution of the model"
)
raise ValueError
@@ -126,17 +130,31 @@ class GeometryObjectsRead(UserObjectGeometry):
rigidE = f["/rigidE"][:]
rigidH = f["/rigidH"][:]
ID = f["/ID"][:]
grid.solid[xs : xs + data.shape[0], ys : ys + data.shape[1], zs : zs + data.shape[2]] = (
data + numexistmaterials
)
grid.rigidE[:, xs : xs + rigidE.shape[1], ys : ys + rigidE.shape[2], zs : zs + rigidE.shape[3]] = rigidE
grid.rigidH[:, xs : xs + rigidH.shape[1], ys : ys + rigidH.shape[2], zs : zs + rigidH.shape[3]] = rigidH
grid.ID[:, xs : xs + ID.shape[1], ys : ys + ID.shape[2], zs : zs + ID.shape[3]] = ID + numexistmaterials
grid.solid[
xs : xs + data.shape[0],
ys : ys + data.shape[1],
zs : zs + data.shape[2],
] = data + numexistmaterials
grid.rigidE[
:,
xs : xs + rigidE.shape[1],
ys : ys + rigidE.shape[2],
zs : zs + rigidE.shape[3],
] = rigidE
grid.rigidH[
:,
xs : xs + rigidH.shape[1],
ys : ys + rigidH.shape[2],
zs : zs + rigidH.shape[3],
] = rigidH
grid.ID[
:, xs : xs + ID.shape[1], ys : ys + ID.shape[2], zs : zs + ID.shape[3]
] = ID + numexistmaterials
logger.info(
f"{self.grid_name(grid)}Geometry objects from file {geofile} "
+ f"inserted at {xs * grid.dx:g}m, {ys * grid.dy:g}m, "
+ f"{zs * grid.dz:g}m, with corresponding materials file "
+ f"{matfile}."
f"inserted at {xs * grid.dx:g}m, {ys * grid.dy:g}m, "
f"{zs * grid.dz:g}m, with corresponding materials file "
f"{matfile}."
)
except KeyError:
averaging = False
@@ -144,7 +162,6 @@ class GeometryObjectsRead(UserObjectGeometry):
xs,
ys,
zs,
config.get_model_config().ompthreads,
numexistmaterials,
averaging,
data,
@@ -155,7 +172,7 @@ class GeometryObjectsRead(UserObjectGeometry):
)
logger.info(
f"{self.grid_name(grid)}Geometry objects from file "
+ f"(voxels only){geofile} inserted at {xs * grid.dx:g}m, "
+ f"{ys * grid.dy:g}m, {zs * grid.dz:g}m, with corresponding "
+ f"materials file {matfile}."
f"(voxels only){geofile} inserted at {xs * grid.dx:g}m, "
f"{ys * grid.dy:g}m, {zs * grid.dz:g}m, with corresponding "
f"materials file {matfile}."
)

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -55,7 +55,7 @@ class Plate(UserObjectGeometry):
self.kwargs["p1"] = tuple(rot_pts[0, :])
self.kwargs["p2"] = tuple(rot_pts[1, :])
def create(self, grid, uip):
def build(self, grid, uip):
try:
p1 = self.kwargs["p1"]
p2 = self.kwargs["p2"]
@@ -114,7 +114,9 @@ class Plate(UserObjectGeometry):
for j in range(ys, yf):
for k in range(zs, zf):
build_face_yz(xs, j, k, numIDy, numIDz, grid.rigidE, grid.rigidH, grid.ID)
build_face_yz(
xs, j, k, numIDy, numIDz, grid.rigidE, grid.rigidH, grid.ID
)
# xz-plane plate
elif ys == yf:
@@ -129,7 +131,9 @@ class Plate(UserObjectGeometry):
for i in range(xs, xf):
for k in range(zs, zf):
build_face_xz(i, ys, k, numIDx, numIDz, grid.rigidE, grid.rigidH, grid.ID)
build_face_xz(
i, ys, k, numIDx, numIDz, grid.rigidE, grid.rigidH, grid.ID
)
# xy-plane plate
elif zs == zf:
@@ -144,10 +148,12 @@ class Plate(UserObjectGeometry):
for i in range(xs, xf):
for j in range(ys, yf):
build_face_xy(i, j, zs, numIDx, numIDy, grid.rigidE, grid.rigidH, grid.ID)
build_face_xy(
i, j, zs, numIDx, numIDy, grid.rigidE, grid.rigidH, grid.ID
)
logger.info(
f"{self.grid_name(grid)}Plate from {p3[0]:g}m, {p3[1]:g}m, "
+ f"{p3[2]:g}m, to {p4[0]:g}m, {p4[1]:g}m, {p4[2]:g}m of "
+ f"material(s) {', '.join(materialsrequested)} created."
f"{p3[2]:g}m, to {p4[0]:g}m, {p4[1]:g}m, {p4[2]:g}m of "
f"material(s) {', '.join(materialsrequested)} created."
)

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -22,7 +22,7 @@ import numpy as np
from ..cython.geometry_primitives import build_sphere
from ..materials import Material
from .cmds_geometry import UserObjectGeometry
from .cmds_geometry import UserObjectGeometry, check_averaging
logger = logging.getLogger(__name__)
@@ -43,7 +43,7 @@ class Sphere(UserObjectGeometry):
super().__init__(**kwargs)
self.hash = "#sphere"
def create(self, grid, uip):
def build(self, grid, uip):
try:
p1 = self.kwargs["p1"]
r = self.kwargs["r"]
@@ -55,6 +55,7 @@ class Sphere(UserObjectGeometry):
try:
# Try user-specified averaging
averagesphere = self.kwargs["averaging"]
averagesphere = check_averaging(averagesphere)
except KeyError:
# Otherwise go with the grid default
averagesphere = grid.averagevolumeobjects
@@ -103,10 +104,18 @@ class Sphere(UserObjectGeometry):
m = Material(numID, requiredID)
m.type = "dielectric-smoothed"
# Create dielectric-smoothed constituents for material
m.er = np.mean((materials[0].er, materials[1].er, materials[2].er), axis=0)
m.se = np.mean((materials[0].se, materials[1].se, materials[2].se), axis=0)
m.mr = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0)
m.sm = np.mean((materials[0].sm, materials[1].sm, materials[2].sm), axis=0)
m.er = np.mean(
(materials[0].er, materials[1].er, materials[2].er), axis=0
)
m.se = np.mean(
(materials[0].se, materials[1].se, materials[2].se), axis=0
)
m.mr = np.mean(
(materials[0].mr, materials[1].mr, materials[2].mr), axis=0
)
m.sm = np.mean(
(materials[0].sm, materials[1].sm, materials[2].sm), axis=0
)
# Append the new material object to the materials list
grid.materials.append(m)
@@ -133,7 +142,7 @@ class Sphere(UserObjectGeometry):
dielectricsmoothing = "on" if averaging else "off"
logger.info(
f"{self.grid_name(grid)}Sphere with centre {p2[0]:g}m, "
+ f"{p2[1]:g}m, {p2[2]:g}m, radius {r:g}m, of material(s) "
+ f"{', '.join(materialsrequested)} created, dielectric "
+ f"smoothing is {dielectricsmoothing}."
f"{p2[1]:g}m, {p2[2]:g}m, radius {r:g}m, of material(s) "
f"{', '.join(materialsrequested)} created, dielectric "
f"smoothing is {dielectricsmoothing}."
)

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -22,7 +22,7 @@ import numpy as np
from ..cython.geometry_primitives import build_triangle
from ..materials import Material
from .cmds_geometry import UserObjectGeometry, rotate_point
from .cmds_geometry import UserObjectGeometry, check_averaging, rotate_point
logger = logging.getLogger(__name__)
@@ -63,7 +63,7 @@ class Triangle(UserObjectGeometry):
self.kwargs["p2"] = tuple(p2)
self.kwargs["p3"] = tuple(p3)
def create(self, grid, uip):
def build(self, grid, uip):
try:
up1 = self.kwargs["p1"]
up2 = self.kwargs["p2"]
@@ -80,6 +80,7 @@ class Triangle(UserObjectGeometry):
try:
# Try user-specified averaging
averagetriangularprism = self.kwargs["averaging"]
averagetriangularprism = check_averaging(averagetriangularprism)
except KeyError:
# Otherwise go with the grid default
averagetriangularprism = grid.averagevolumeobjects
@@ -108,7 +109,9 @@ class Triangle(UserObjectGeometry):
x3, y3, z3 = uip.round_to_grid(up3)
if thickness < 0:
logger.exception(f"{self.__str__()} requires a positive value for thickness")
logger.exception(
f"{self.__str__()} requires a positive value for thickness"
)
raise ValueError
# Check for valid orientations
@@ -122,7 +125,9 @@ class Triangle(UserObjectGeometry):
elif z1 == z2 == z3:
normal = "z"
else:
logger.exception(f"{self.__str__()} the triangle is not specified correctly")
logger.exception(
f"{self.__str__()} the triangle is not specified correctly"
)
raise ValueError
# Look up requested materials in existing list of material instances
@@ -145,7 +150,9 @@ class Triangle(UserObjectGeometry):
numIDx = materials[0].numID
numIDy = materials[1].numID
numIDz = materials[2].numID
requiredID = materials[0].ID + "+" + materials[1].ID + "+" + materials[2].ID
requiredID = (
materials[0].ID + "+" + materials[1].ID + "+" + materials[2].ID
)
averagedmaterial = [x for x in grid.materials if x.ID == requiredID]
if averagedmaterial:
numID = averagedmaterial.numID
@@ -154,10 +161,18 @@ class Triangle(UserObjectGeometry):
m = Material(numID, requiredID)
m.type = "dielectric-smoothed"
# Create dielectric-smoothed constituents for material
m.er = np.mean((materials[0].er, materials[1].er, materials[2].er), axis=0)
m.se = np.mean((materials[0].se, materials[1].se, materials[2].se), axis=0)
m.mr = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0)
m.sm = np.mean((materials[0].sm, materials[1].sm, materials[2].sm), axis=0)
m.er = np.mean(
(materials[0].er, materials[1].er, materials[2].er), axis=0
)
m.se = np.mean(
(materials[0].se, materials[1].se, materials[2].se), axis=0
)
m.mr = np.mean(
(materials[0].mr, materials[1].mr, materials[2].mr), axis=0
)
m.sm = np.mean(
(materials[0].sm, materials[1].sm, materials[2].sm), axis=0
)
# Append the new material object to the materials list
grid.materials.append(m)
@@ -205,15 +220,15 @@ class Triangle(UserObjectGeometry):
dielectricsmoothing = "on" if averaging else "off"
logger.info(
f"{self.grid_name(grid)}Triangle with coordinates "
+ f"{p4[0]:g}m {p4[1]:g}m {p4[2]:g}m, {p5[0]:g}m {p5[1]:g}m "
+ f"{p5[2]:g}m, {p6[0]:g}m {p6[1]:g}m {p6[2]:g}m and thickness "
+ f"{thickness:g}m of material(s) {', '.join(materialsrequested)} "
+ f"created, dielectric smoothing is {dielectricsmoothing}."
f"{p4[0]:g}m {p4[1]:g}m {p4[2]:g}m, {p5[0]:g}m {p5[1]:g}m "
f"{p5[2]:g}m, {p6[0]:g}m {p6[1]:g}m {p6[2]:g}m and thickness "
f"{thickness:g}m of material(s) {', '.join(materialsrequested)} "
f"created, dielectric smoothing is {dielectricsmoothing}."
)
else:
logger.info(
f"{self.grid_name(grid)}Triangle with coordinates "
+ f"{p4[0]:g}m {p4[1]:g}m {p4[2]:g}m, {p5[0]:g}m {p5[1]:g}m "
+ f"{p5[2]:g}m, {p6[0]:g}m {p6[1]:g}m {p6[2]:g}m of material(s) "
+ f"{', '.join(materialsrequested)} created."
f"{p4[0]:g}m {p4[1]:g}m {p4[2]:g}m, {p5[0]:g}m {p5[1]:g}m "
f"{p5[2]:g}m, {p6[0]:g}m {p6[1]:g}m {p6[2]:g}m of material(s) "
f"{', '.join(materialsrequested)} created."
)

文件差异内容过多而无法显示 加载差异

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -16,18 +16,14 @@
# You should have received a copy of the GNU General Public License
# along with gprMax. If not, see <http://www.gnu.org/licenses/>.
import inspect
import logging
from pathlib import Path
import numpy as np
from scipy import interpolate
import gprMax.config as config
from .pml import PML
from .utilities.host_info import set_omp_threads
from .waveforms import Waveform
logger = logging.getLogger(__name__)
@@ -51,7 +47,7 @@ class UserObjectSingle:
for k, v in kwargs.items():
setattr(self.props, k, v)
def create(self, grid, uip):
def build(self, grid, uip):
pass
def rotate(self, axis, angle, origin=None):
@@ -69,7 +65,7 @@ class Title(UserObjectSingle):
super().__init__(**kwargs)
self.order = 1
def create(self, G, uip):
def build(self, G, uip):
try:
title = self.kwargs["name"]
G.title = title
@@ -89,7 +85,7 @@ class Discretisation(UserObjectSingle):
super().__init__(**kwargs)
self.order = 2
def create(self, G, uip):
def build(self, G, uip):
try:
G.dl = np.array(self.kwargs["p1"])
G.dx, G.dy, G.dz = self.kwargs["p1"]
@@ -99,17 +95,20 @@ class Discretisation(UserObjectSingle):
if G.dl[0] <= 0:
logger.exception(
f"{self.__str__()} discretisation requires the " f"x-direction spatial step to be greater than zero"
f"{self.__str__()} discretisation requires the "
f"x-direction spatial step to be greater than zero"
)
raise ValueError
if G.dl[1] <= 0:
logger.exception(
f"{self.__str__()} discretisation requires the " f"y-direction spatial step to be greater than zero"
f"{self.__str__()} discretisation requires the "
f"y-direction spatial step to be greater than zero"
)
raise ValueError
if G.dl[2] <= 0:
logger.exception(
f"{self.__str__()} discretisation requires the " f"z-direction spatial step to be greater than zero"
f"{self.__str__()} discretisation requires the "
f"z-direction spatial step to be greater than zero"
)
raise ValueError
@@ -127,7 +126,7 @@ class Domain(UserObjectSingle):
super().__init__(**kwargs)
self.order = 3
def create(self, G, uip):
def build(self, G, uip):
try:
G.nx, G.ny, G.nz = uip.discretise_point(self.kwargs["p1"])
except KeyError:
@@ -135,7 +134,9 @@ class Domain(UserObjectSingle):
raise
if G.nx == 0 or G.ny == 0 or G.nz == 0:
logger.exception(f"{self.__str__()} requires at least one cell in " f"every dimension")
logger.exception(
f"{self.__str__()} requires at least one cell in every dimension"
)
raise ValueError
logger.info(
@@ -165,7 +166,10 @@ class Domain(UserObjectSingle):
# Sub-grids cannot be used with 2D models. There would typically be
# minimal performance benefit with sub-gridding and 2D models.
if "2D" in config.get_model_config().mode and config.sim_config.general["subgrid"]:
if (
"2D" in config.get_model_config().mode
and config.sim_config.general["subgrid"]
):
logger.exception("Sub-gridding cannot be used with 2D models")
raise ValueError
@@ -183,7 +187,7 @@ class TimeStepStabilityFactor(UserObjectSingle):
super().__init__(**kwargs)
self.order = 4
def create(self, G, uip):
def build(self, G, uip):
try:
f = self.kwargs["f"]
except KeyError:
@@ -192,7 +196,8 @@ class TimeStepStabilityFactor(UserObjectSingle):
if f <= 0 or f > 1:
logger.exception(
f"{self.__str__()} requires the value of the time " f"step stability factor to be between zero and one"
f"{self.__str__()} requires the value of the time "
f"step stability factor to be between zero and one"
)
raise ValueError
@@ -214,7 +219,7 @@ class TimeWindow(UserObjectSingle):
super().__init__(**kwargs)
self.order = 5
def create(self, G, uip):
def build(self, G, uip):
# If number of iterations given
# The +/- 1 used in calculating the number of iterations is to account for
# the fact that the solver (iterations) loop runs from 0 to < G.iterations
@@ -231,7 +236,9 @@ class TimeWindow(UserObjectSingle):
G.timewindow = tmp
G.iterations = int(np.ceil(tmp / G.dt)) + 1
else:
logger.exception(self.__str__() + " must have a value greater than zero")
logger.exception(
self.__str__() + " must have a value greater than zero"
)
raise ValueError
except KeyError:
pass
@@ -255,7 +262,7 @@ class OMPThreads(UserObjectSingle):
super().__init__(**kwargs)
self.order = 6
def create(self, G, uip):
def build(self, G, uip):
try:
n = self.kwargs["n"]
except KeyError:
@@ -265,7 +272,10 @@ class OMPThreads(UserObjectSingle):
)
raise
if n < 1:
logger.exception(f"{self.__str__()} requires the value to be an " f"integer not less than one")
logger.exception(
f"{self.__str__()} requires the value to be an "
f"integer not less than one"
)
raise ValueError
config.get_model_config().ompthreads = set_omp_threads(n)
@@ -289,12 +299,14 @@ class PMLProps(UserObjectSingle):
super().__init__(**kwargs)
self.order = 7
def create(self, G, uip):
def build(self, G, uip):
try:
G.pmls["formulation"] = self.kwargs["formulation"]
if G.pmls["formulation"] not in PML.formulations:
logger.exception(
self.__str__() + f" requires the value to be " + f"one of {' '.join(PML.formulations)}"
self.__str__()
+ f" requires the value to be "
+ f"one of {' '.join(PML.formulations)}"
)
except KeyError:
pass
@@ -313,7 +325,9 @@ class PMLProps(UserObjectSingle):
G.pmls["thickness"]["ymax"] = int(self.kwargs["ymax"])
G.pmls["thickness"]["zmax"] = int(self.kwargs["zmax"])
except KeyError:
logger.exception(f"{self.__str__()} requires either one or six parameter(s)")
logger.exception(
f"{self.__str__()} requires either one or six parameter(s)"
)
raise
if (
@@ -339,7 +353,7 @@ class SrcSteps(UserObjectSingle):
super().__init__(**kwargs)
self.order = 8
def create(self, G, uip):
def build(self, G, uip):
try:
G.srcsteps = uip.discretise_point(self.kwargs["p1"])
except KeyError:
@@ -348,8 +362,8 @@ class SrcSteps(UserObjectSingle):
logger.info(
f"Simple sources will step {G.srcsteps[0] * G.dx:g}m, "
+ f"{G.srcsteps[1] * G.dy:g}m, {G.srcsteps[2] * G.dz:g}m "
+ "for each model run."
f"{G.srcsteps[1] * G.dy:g}m, {G.srcsteps[2] * G.dz:g}m "
"for each model run."
)
@@ -364,7 +378,7 @@ class RxSteps(UserObjectSingle):
super().__init__(**kwargs)
self.order = 9
def create(self, G, uip):
def build(self, G, uip):
try:
G.rxsteps = uip.discretise_point(self.kwargs["p1"])
except KeyError:
@@ -373,102 +387,11 @@ class RxSteps(UserObjectSingle):
logger.info(
f"All receivers will step {G.rxsteps[0] * G.dx:g}m, "
+ f"{G.rxsteps[1] * G.dy:g}m, {G.rxsteps[2] * G.dz:g}m "
+ "for each model run."
f"{G.rxsteps[1] * G.dy:g}m, {G.rxsteps[2] * G.dz:g}m "
"for each model run."
)
class ExcitationFile(UserObjectSingle):
"""An ASCII file that contains columns of amplitude values that specify
custom waveform shapes that can be used with sources in the model.
Attributes:
filepath: string of excitation file path.
kind: string or int specifying interpolation kind passed to
scipy.interpolate.interp1d.
fill_value: float or 'extrapolate' passed to scipy.interpolate.interp1d.
"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.order = 10
def create(self, G, uip):
try:
kwargs = {}
excitationfile = self.kwargs["filepath"]
kwargs["kind"] = self.kwargs["kind"]
kwargs["fill_value"] = self.kwargs["fill_value"]
except KeyError:
try:
excitationfile = self.kwargs["filepath"]
fullargspec = inspect.getfullargspec(interpolate.interp1d)
kwargs = dict(zip(reversed(fullargspec.args), reversed(fullargspec.defaults)))
except KeyError:
logger.exception(f"{self.__str__()} requires either one or three parameter(s)")
raise
# See if file exists at specified path and if not try input file directory
excitationfile = Path(excitationfile)
# excitationfile = excitationfile.resolve()
if not excitationfile.exists():
excitationfile = Path(config.sim_config.input_file_path.parent, excitationfile)
logger.info(f"Excitation file: {excitationfile}")
# Get waveform names
with open(excitationfile, "r") as f:
waveformIDs = f.readline().split()
# Read all waveform values into an array
waveformvalues = np.loadtxt(excitationfile, skiprows=1, dtype=config.sim_config.dtypes["float_or_double"])
# Time array (if specified) for interpolation, otherwise use simulation time
if waveformIDs[0].lower() == "time":
waveformIDs = waveformIDs[1:]
waveformtime = waveformvalues[:, 0]
waveformvalues = waveformvalues[:, 1:]
timestr = "user-defined time array"
else:
waveformtime = np.arange(0, G.timewindow + G.dt, G.dt)
timestr = "simulation time array"
for waveform in range(len(waveformIDs)):
if any(x.ID == waveformIDs[waveform] for x in G.waveforms):
logger.exception(f"Waveform with ID {waveformIDs[waveform]} already exists")
raise ValueError
w = Waveform()
w.ID = waveformIDs[waveform]
w.type = "user"
# Select correct column of waveform values depending on array shape
singlewaveformvalues = waveformvalues[:] if len(waveformvalues.shape) == 1 else waveformvalues[:, waveform]
# Truncate waveform array if it is longer than time array
if len(singlewaveformvalues) > len(waveformtime):
singlewaveformvalues = singlewaveformvalues[: len(waveformtime)]
# Zero-pad end of waveform array if it is shorter than time array
elif len(singlewaveformvalues) < len(waveformtime):
singlewaveformvalues = np.pad(
singlewaveformvalues,
(0, len(waveformtime) - len(singlewaveformvalues)),
"constant",
constant_values=0,
)
# Interpolate waveform values
w.userfunc = interpolate.interp1d(waveformtime, singlewaveformvalues, **kwargs)
logger.info(
f"User waveform {w.ID} created using {timestr} and, if "
+ f"required, interpolation parameters (kind: {kwargs['kind']}, "
+ f"fill value: {kwargs['fill_value']})."
)
G.waveforms.append(w)
class OutputDir(UserObjectSingle):
"""Controls the directory where output file(s) will be stored.
@@ -478,7 +401,7 @@ class OutputDir(UserObjectSingle):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.order = 11
self.order = 10
def create(self, grid, uip):
def build(self, grid, uip):
config.get_model_config().set_output_file_path(self.kwargs["dir"])

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -21,9 +21,6 @@ import sys
import warnings
from pathlib import Path
# Used to suppress CompilerWarning (sub-class of UserWarning) from pyopencl
warnings.filterwarnings("ignore", category=UserWarning)
import cython
import numpy as np
from colorama import Fore, Style, init
@@ -90,7 +87,11 @@ class ModelConfig:
except:
deviceID = 0
self.device = {"dev": sim_config.set_model_device(deviceID), "snapsgpu2cpu": False}
self.device = {
"dev": sim_config.set_model_device(deviceID),
"deviceID": deviceID,
"snapsgpu2cpu": False,
}
# Total memory usage for all grids in the model. Starts with 50MB overhead.
self.mem_overhead = 65e6
@@ -99,11 +100,20 @@ class ModelConfig:
self.reuse_geometry = False
# String to print at start of each model run
s = f"\n--- Model {model_num + 1}/{sim_config.model_end}, " f"input file: {sim_config.input_file_path}"
self.inputfilestr = Fore.GREEN + f"{s} {'-' * (get_terminal_width() - 1 - len(s))}\n" + Style.RESET_ALL
s = (
f"\n--- Model {model_num + 1}/{sim_config.model_end}, "
f"input file: {sim_config.input_file_path}"
)
self.inputfilestr = (
Fore.GREEN
+ f"{s} {'-' * (get_terminal_width() - 1 - len(s))}\n"
+ Style.RESET_ALL
)
# Output file path and name for specific model
self.appendmodelnumber = "" if sim_config.args.n == 1 else str(model_num + 1) # Indexed from 1
self.appendmodelnumber = (
"" if sim_config.args.n == 1 else str(model_num + 1)
) # Indexed from 1
self.set_output_file_path()
# Numerical dispersion analysis parameters
@@ -114,27 +124,31 @@ class ModelConfig:
# phase-velocity phase error.
# mingridsampling: minimum grid sampling of smallest wavelength for
# physical wave propagation.
self.numdispersion = {"highestfreqthres": 40, "maxnumericaldisp": 2, "mingridsampling": 3}
self.numdispersion = {
"highestfreqthres": 40,
"maxnumericaldisp": 2,
"mingridsampling": 3,
}
# General information to configure materials
# maxpoles: Maximum number of dispersive material poles in a model.
# dispersivedtype: Data type for dispersive materials.
# dispersiveCdtype: Data type for dispersive materials in Cython.
# drudelorentz: True/False model contains Drude or Lorentz materials.
# cudarealfunc: String to substitute into CUDA kernels for fields
# crealfunc: String to substitute into CUDA/OpenCL kernels for fields
# dependent on dispersive material type.
self.materials = {
"maxpoles": 0,
"dispersivedtype": None,
"dispersiveCdtype": None,
"drudelorentz": None,
"cudarealfunc": "",
"crealfunc": None,
}
def get_scene(self):
if sim_config.scenes:
try:
return sim_config.scenes[model_num]
else:
except:
return None
def get_usernamespace(self):
@@ -209,33 +223,33 @@ class SimulationConfig:
self.args = args
if args.mpi and args.geometry_fixed:
if self.args.mpi and self.args.geometry_fixed:
logger.exception("The geometry fixed option cannot be used with MPI.")
raise ValueError
if args.gpu and args.opencl:
if self.args.gpu and self.args.opencl:
logger.exception("You cannot use both CUDA and OpenCl simultaneously.")
raise ValueError
# General settings for the simulation
# inputfilepath: path to inputfile location.
# outputfilepath: path to outputfile location.
# progressbars: whether to show progress bars on stdoout or not.
# solver: cpu, cuda, opencl.
# subgrid: whether the simulation uses sub-grids.
# precision: data type for electromagnetic field output (single/double).
# progressbars: progress bars on stdoout or not - switch off
# progressbars when logging level is greater than
# info (20)
self.general = {"solver": "cpu", "subgrid": False, "precision": "single", "progressbars": args.log_level <= 20}
self.general = {
"solver": "cpu",
"precision": "single",
"progressbars": args.log_level <= 20,
}
self.em_consts = {
"c": c, # Speed of light in free space (m/s)
"e0": e0, # Permittivity of free space (F/m)
"m0": m0, # Permeability of free space (H/m)
"z0": np.sqrt(m0 / e0),
} # Impedance of free space (Ohms)
"z0": np.sqrt(m0 / e0), # Impedance of free space (Ohms)
}
# Store information about host machine
self.hostinfo = get_host_info()
@@ -246,7 +260,10 @@ class SimulationConfig:
# Both single and double precision are possible on GPUs, but single
# provides best performance.
self.general["precision"] = "single"
self.devices = {"devs": [], "nvcc_opts": None} # pycuda device objects # nvcc compiler options
self.devices = {
"devs": [],
"nvcc_opts": None,
} # pycuda device objects; nvcc compiler options
# Suppress nvcc warnings on Microsoft Windows
if sys.platform == "win32":
self.devices["nvcc_opts"] = ["-w"]
@@ -258,7 +275,13 @@ class SimulationConfig:
if self.args.opencl is not None:
self.general["solver"] = "opencl"
self.general["precision"] = "single"
self.devices = {"devs": [], "compiler_opts": None} # pyopencl available device(s)
self.devices = {
"devs": [],
"compiler_opts": None,
} # pyopencl device device(s); compiler options
# Suppress CompilerWarning (sub-class of UserWarning)
warnings.filterwarnings("ignore", category=UserWarning)
# Suppress unused variable warnings on gcc
# if sys.platform != 'win32': self.devices['compiler_opts'] = ['-w']
@@ -266,8 +289,8 @@ class SimulationConfig:
# Add pyopencl available device(s)
self.devices["devs"] = detect_opencl()
# Subgrid parameter may not exist if user enters via CLI
try:
# Subgrids
if hasattr(self.args, "subgrid") and self.args.subgrid:
self.general["subgrid"] = self.args.subgrid
# Double precision should be used with subgrid for best accuracy
self.general["precision"] = "double"
@@ -275,10 +298,11 @@ class SimulationConfig:
self.general["subgrid"] and self.general["solver"] == "opencl"
):
logger.exception(
"You cannot currently use CUDA or OpenCL-based " "solvers with models that contain sub-grids."
"You cannot currently use CUDA or OpenCL-based "
"solvers with models that contain sub-grids."
)
raise ValueError
except AttributeError:
else:
self.general["subgrid"] = False
# Scenes parameter may not exist if user enters via CLI
@@ -309,7 +333,9 @@ class SimulationConfig:
return dev
if not found:
logger.exception(f"Compute device with device ID {deviceID} does " "not exist.")
logger.exception(
f"Compute device with device ID {deviceID} does not exist."
)
raise ValueError
def _set_precision(self):

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -17,10 +17,14 @@
# along with gprMax. If not, see <http://www.gnu.org/licenses/>.
import datetime
import gc
import logging
import sys
import humanize
from colorama import Fore, Style, init
init()
import gprMax.config as config
@@ -40,7 +44,9 @@ class Context:
"""
def __init__(self):
self.model_range = range(config.sim_config.model_start, config.sim_config.model_end)
self.model_range = range(
config.sim_config.model_start, config.sim_config.model_end
)
self.tsimend = None
self.tsimstart = None
@@ -59,7 +65,7 @@ class Context:
elif config.sim_config.general["solver"] == "opencl":
print_opencl_info(config.sim_config.devices["devs"])
# Clear list of model configs. It can be retained when gprMax is
# Clear list of model configs, which can be retained when gprMax is
# called in a loop, and want to avoid this.
config.model_configs = []
@@ -81,6 +87,14 @@ class Context:
if not config.sim_config.args.geometry_only:
solver = create_solver(G)
model.solve(solver)
del solver, model
if not config.sim_config.args.geometry_fixed:
# Manual garbage collection required to stop memory leak on GPUs
# when using pycuda
del G
gc.collect()
self.tsimend = timer()
self.print_sim_time_taken()
@@ -96,7 +110,7 @@ class Context:
"""Prints the total simulation time based on context."""
s = (
f"\n=== Simulation completed in "
+ f"{humanize.precisedelta(datetime.timedelta(seconds=self.tsimend - self.tsimstart), format='%0.4f')}"
f"{humanize.precisedelta(datetime.timedelta(seconds=self.tsimend - self.tsimstart), format='%0.4f')}"
)
logger.basic(f"{s} {'=' * (get_terminal_width() - 1 - len(s))}\n")
@@ -130,7 +144,11 @@ class MPIContext(Context):
model_config = config.ModelConfig()
# Set GPU deviceID according to worker rank
if config.sim_config.general["solver"] == "cuda":
model_config.device = {"dev": config.sim_config.devices["devs"][self.rank - 1], "snapsgpu2cpu": False}
model_config.device = {
"dev": config.sim_config.devices["devs"][self.rank - 1],
"deviceID": self.rank - 1,
"snapsgpu2cpu": False,
}
config.model_configs = model_config
G = create_G()
@@ -140,6 +158,12 @@ class MPIContext(Context):
if not config.sim_config.args.geometry_only:
solver = create_solver(G)
model.solve(solver)
del solver, model
# Manual garbage collection required to stop memory leak on GPUs when
# using pycuda
del G
gc.collect()
def run(self):
"""Specialise how the models are run.
@@ -156,6 +180,14 @@ class MPIContext(Context):
print_cuda_info(config.sim_config.devices["devs"])
elif config.sim_config.general["solver"] == "opencl":
print_opencl_info(config.sim_config.devices["devs"])
s = f"\n--- Input file: {config.sim_config.input_file_path}"
logger.basic(
Fore.GREEN
+ f"{s} {'-' * (get_terminal_width() - 1 - len(s))}\n"
+ Style.RESET_ALL
)
sys.stdout.flush()
# Contruct MPIExecutor

查看文件

@@ -9,7 +9,7 @@
#define IDX2D_SRCWAVES(m, n) (m)*({{NY_SRCWAVES}})+(n)
#define IDX3D_FIELDS(i, j, k) (i)*({{NY_FIELDS}})*({{NZ_FIELDS}})+(j)*({{NZ_FIELDS}})+(k)
#define IDX3D_RXS(i,j,k) (i)*({{NY_RXS}})*({{NZ_RXS}})+(j)*({{NZ_RXS}})+(k)
#define IDX3D_RXS(i, j, k) (i)*({{NY_RXS}})*({{NZ_RXS}})+(j)*({{NZ_RXS}})+(k)
#define IDX4D_ID(p, i, j, k) (p)*({{NX_ID}})*({{NY_ID}})*({{NZ_ID}})+(i)*({{NY_ID}})*({{NZ_ID}})+(j)*({{NZ_ID}})+(k)
#define IDX4D_SNAPS(p, i, j, k) (p)*({{NX_SNAPS}})*({{NY_SNAPS}})*({{NZ_SNAPS}})+(i)*({{NY_SNAPS}})*({{NZ_SNAPS}})+(j)*({{NZ_SNAPS}})+(k)

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -42,9 +42,9 @@ update_electric = {
__global $REAL *Ex,
__global $REAL *Ey,
__global $REAL *Ez,
__global const $REAL * restrict Hx,
__global const $REAL * restrict Hy,
__global const $REAL * restrict Hz
__global const $REAL* restrict Hx,
__global const $REAL* restrict Hy,
__global const $REAL* restrict Hz
"""
),
"func": Template(
@@ -118,9 +118,9 @@ update_magnetic = {
__global $REAL *Hx,
__global $REAL *Hy,
__global $REAL *Hz,
__global const $REAL * restrict Ex,
__global const $REAL * restrict Ey,
__global const $REAL * restrict Ez
__global const $REAL* restrict Ex,
__global const $REAL* restrict Ey,
__global const $REAL* restrict Ez
"""
),
"func": Template(
@@ -196,17 +196,17 @@ update_electric_dispersive_A = {
int NY,
int NZ,
int MAXPOLES,
__global const $COMPLEX* restrict updatecoeffsdispersive,
__global $COMPLEX *Tx,
__global $COMPLEX *Ty,
__global $COMPLEX *Tz,
__global const unsigned int* restrict ID,
__global $REAL *Ex,
__global $REAL *Ey,
__global $REAL *Ez,
__global const $REAL* restrict Hx,
__global const $REAL* restrict Hy,
__global const $REAL* restrict Hz
__global const $REAL* restrict Hz,
__global const $COMPLEX* restrict updatecoeffsdispersive,
__global $COMPLEX *Tx,
__global $COMPLEX *Ty,
__global $COMPLEX *Tz
"""
),
"func": Template(
@@ -238,8 +238,7 @@ update_electric_dispersive_A = {
int x_T = (i % ($NX_T * $NY_T * $NZ_T)) / ($NY_T * $NZ_T);
int y_T = ((i % ($NX_T * $NY_T * $NZ_T)) % ($NY_T * $NZ_T)) / $NZ_T;
int z_T = ((i % ($NX_T * $NY_T * $NZ_T)) % ($NY_T * $NZ_T)) % $NZ_T;
// Ex component
if ((NY != 1 || NZ != 1) && x >= 0 && x < NX && y > 0 && y < NY && z > 0 && z < NZ) {
int materialEx = ID[IDX4D_ID(0,x_ID,y_ID,z_ID)];
@@ -311,14 +310,14 @@ update_electric_dispersive_B = {
int NY,
int NZ,
int MAXPOLES,
__global const $COMPLEX* restrict updatecoeffsdispersive,
__global $COMPLEX *Tx,
__global $COMPLEX *Ty,
__global $COMPLEX *Tz,
__global const unsigned int* restrict ID,
__global const $REAL* restrict Ex,
__global const $REAL* restrict Ey,
__global const $REAL* restrict Ez
__global const $REAL* restrict Ez,
__global const $COMPLEX* restrict updatecoeffsdispersive,
__global $COMPLEX *Tx,
__global $COMPLEX *Ty,
__global $COMPLEX *Tz
"""
),
"func": Template(

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -35,11 +35,19 @@ x_args = {
int NZ_PHI2,
int NY_R,
const unsigned int* __restrict__ ID,
const $REAL* __restrict__ Ex, $REAL *Ey,
const $REAL* __restrict__ Ex,
$REAL *Ey,
$REAL *Ez,
const $REAL* __restrict__ Hx, const $REAL* __restrict__ Hy, const $REAL* __restrict__ Hz, $REAL *PHI1,
const $REAL* __restrict__ Hx,
const $REAL* __restrict__ Hy,
const $REAL* __restrict__ Hz,
$REAL *PHI1,
$REAL *PHI2,
const $REAL* __restrict__ RA, const $REAL* __restrict__ RB, const $REAL* __restrict__ RE, const $REAL* __restrict__ RF, $REAL d)
const $REAL* __restrict__ RA,
const $REAL* __restrict__ RB,
const $REAL* __restrict__ RE,
const $REAL* __restrict__ RF,
$REAL d)
"""
),
"opencl": Template(
@@ -95,9 +103,16 @@ y_args = {
$REAL *Ex,
const $REAL* __restrict__ Ey,
$REAL *Ez,
const $REAL* __restrict__ Hx, const $REAL* __restrict__ Hy, const $REAL* __restrict__ Hz, $REAL *PHI1,
const $REAL* __restrict__ Hx,
const $REAL* __restrict__ Hy,
const $REAL* __restrict__ Hz,
$REAL *PHI1,
$REAL *PHI2,
const $REAL* __restrict__ RA, const $REAL* __restrict__ RB, const $REAL* __restrict__ RE, const $REAL* __restrict__ RF, $REAL d)
const $REAL* __restrict__ RA,
const $REAL* __restrict__ RB,
const $REAL* __restrict__ RE,
const $REAL* __restrict__ RF,
$REAL d)
"""
),
"opencl": Template(
@@ -153,9 +168,16 @@ z_args = {
$REAL *Ex,
$REAL *Ey,
const $REAL* __restrict__ Ez,
const $REAL* __restrict__ Hx, const $REAL* __restrict__ Hy, const $REAL* __restrict__ Hz, $REAL *PHI1,
const $REAL* __restrict__ Hx,
const $REAL* __restrict__ Hy,
const $REAL* __restrict__ Hz,
$REAL *PHI1,
$REAL *PHI2,
const $REAL* __restrict__ RA, const $REAL* __restrict__ RB, const $REAL* __restrict__ RE, const $REAL* __restrict__ RF, $REAL d)
const $REAL* __restrict__ RA,
const $REAL* __restrict__ RB,
const $REAL* __restrict__ RE,
const $REAL* __restrict__ RF,
$REAL d)
"""
),
"opencl": Template(

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -35,11 +35,19 @@ x_args = {
int NZ_PHI2,
int NY_R,
const unsigned int* __restrict__ ID,
const $REAL* __restrict__ Ex, $REAL *Ey,
const $REAL* __restrict__ Ex,
$REAL *Ey,
$REAL *Ez,
const $REAL* __restrict__ Hx, const $REAL* __restrict__ Hy, const $REAL* __restrict__ Hz, $REAL *PHI1,
const $REAL* __restrict__ Hx,
const $REAL* __restrict__ Hy,
const $REAL* __restrict__ Hz,
$REAL *PHI1,
$REAL *PHI2,
const $REAL* __restrict__ RA, const $REAL* __restrict__ RB, const $REAL* __restrict__ RE, const $REAL* __restrict__ RF, $REAL d)
const $REAL* __restrict__ RA,
const $REAL* __restrict__ RB,
const $REAL* __restrict__ RE,
const $REAL* __restrict__ RF,
$REAL d)
"""
),
"opencl": Template(
@@ -95,9 +103,16 @@ y_args = {
$REAL *Ex,
const $REAL* __restrict__ Ey,
$REAL *Ez,
const $REAL* __restrict__ Hx, const $REAL* __restrict__ Hy, const $REAL* __restrict__ Hz, $REAL *PHI1,
const $REAL* __restrict__ Hx,
const $REAL* __restrict__ Hy,
const $REAL* __restrict__ Hz,
$REAL *PHI1,
$REAL *PHI2,
const $REAL* __restrict__ RA, const $REAL* __restrict__ RB, const $REAL* __restrict__ RE, const $REAL* __restrict__ RF, $REAL d)
const $REAL* __restrict__ RA,
const $REAL* __restrict__ RB,
const $REAL* __restrict__ RE,
const $REAL* __restrict__ RF,
$REAL d)
"""
),
"opencl": Template(
@@ -153,9 +168,16 @@ z_args = {
$REAL *Ex,
$REAL *Ey,
const $REAL* __restrict__ Ez,
const $REAL* __restrict__ Hx, const $REAL* __restrict__ Hy, const $REAL* __restrict__ Hz, $REAL *PHI1,
const $REAL* __restrict__ Hx,
const $REAL* __restrict__ Hy,
const $REAL* __restrict__ Hz,
$REAL *PHI1,
$REAL *PHI2,
const $REAL* __restrict__ RA, const $REAL* __restrict__ RB, const $REAL* __restrict__ RE, const $REAL* __restrict__ RF, $REAL d)
const $REAL* __restrict__ RA,
const $REAL* __restrict__ RB,
const $REAL* __restrict__ RE,
const $REAL* __restrict__ RF,
$REAL d)
"""
),
"opencl": Template(

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -35,11 +35,19 @@ x_args = {
int NZ_PHI2,
int NY_R,
const unsigned int* __restrict__ ID,
const $REAL* __restrict__ Ex, const $REAL* __restrict__ Ey, const $REAL* __restrict__ Ez, const $REAL* __restrict__ Hx, $REAL *Hy,
const $REAL* __restrict__ Ex,
const $REAL* __restrict__ Ey,
const $REAL* __restrict__ Ez,
const $REAL* __restrict__ Hx,
$REAL *Hy,
$REAL *Hz,
$REAL *PHI1,
$REAL *PHI2,
const $REAL* __restrict__ RA, const $REAL* __restrict__ RB, const $REAL* __restrict__ RE, const $REAL* __restrict__ RF, $REAL d)
const $REAL* __restrict__ RA,
const $REAL* __restrict__ RB,
const $REAL* __restrict__ RE,
const $REAL* __restrict__ RF,
$REAL d)
"""
),
"opencl": Template(
@@ -92,13 +100,19 @@ y_args = {
int NZ_PHI2,
int NY_R,
const unsigned int* __restrict__ ID,
const $REAL* __restrict__ Ex, const $REAL* __restrict__ Ey, const $REAL* __restrict__ Ez,
const $REAL* __restrict__ Ex,
const $REAL* __restrict__ Ey,
const $REAL* __restrict__ Ez,
$REAL *Hx,
const $REAL* __restrict__ Hy,
$REAL *Hz,
$REAL *PHI1,
$REAL *PHI2,
const $REAL* __restrict__ RA, const $REAL* __restrict__ RB, const $REAL* __restrict__ RE, const $REAL* __restrict__ RF, $REAL d)
const $REAL* __restrict__ RA,
const $REAL* __restrict__ RB,
const $REAL* __restrict__ RE,
const $REAL* __restrict__ RF,
$REAL d)
"""
),
"opencl": Template(
@@ -151,13 +165,19 @@ z_args = {
int NZ_PHI2,
int NY_R,
const unsigned int* __restrict__ ID,
const $REAL* __restrict__ Ex, const $REAL* __restrict__ Ey, const $REAL* __restrict__ Ez,
const $REAL* __restrict__ Ex,
const $REAL* __restrict__ Ey,
const $REAL* __restrict__ Ez,
$REAL *Hx,
$REAL *Hy,
const $REAL* __restrict__ Hz,
$REAL *PHI1,
$REAL *PHI2,
const $REAL* __restrict__ RA, const $REAL* __restrict__ RB, const $REAL* __restrict__ RE, const $REAL* __restrict__ RF, $REAL d)
const $REAL* __restrict__ RA,
const $REAL* __restrict__ RB,
const $REAL* __restrict__ RE,
const $REAL* __restrict__ RF,
$REAL d)
"""
),
"opencl": Template(

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -35,11 +35,19 @@ x_args = {
int NZ_PHI2,
int NY_R,
const unsigned int* __restrict__ ID,
const $REAL* __restrict__ Ex, const $REAL* __restrict__ Ey, const $REAL* __restrict__ Ez, const $REAL* __restrict__ Hx, $REAL *Hy,
const $REAL* __restrict__ Ex,
const $REAL* __restrict__ Ey,
const $REAL* __restrict__ Ez,
const $REAL* __restrict__ Hx,
$REAL *Hy,
$REAL *Hz,
$REAL *PHI1,
$REAL *PHI2,
const $REAL* __restrict__ RA, const $REAL* __restrict__ RB, const $REAL* __restrict__ RE, const $REAL* __restrict__ RF, $REAL d)
const $REAL* __restrict__ RA,
const $REAL* __restrict__ RB,
const $REAL* __restrict__ RE,
const $REAL* __restrict__ RF,
$REAL d)
"""
),
"opencl": Template(
@@ -92,13 +100,19 @@ y_args = {
int NZ_PHI2,
int NY_R,
const unsigned int* __restrict__ ID,
const $REAL* __restrict__ Ex, const $REAL* __restrict__ Ey, const $REAL* __restrict__ Ez,
const $REAL* __restrict__ Ex,
const $REAL* __restrict__ Ey,
const $REAL* __restrict__ Ez,
$REAL *Hx,
const $REAL* __restrict__ Hy,
$REAL *Hz,
$REAL *PHI1,
$REAL *PHI2,
const $REAL* __restrict__ RA, const $REAL* __restrict__ RB, const $REAL* __restrict__ RE, const $REAL* __restrict__ RF, $REAL d)
const $REAL* __restrict__ RA,
const $REAL* __restrict__ RB,
const $REAL* __restrict__ RE,
const $REAL* __restrict__ RF,
$REAL d)
"""
),
"opencl": Template(
@@ -151,13 +165,19 @@ z_args = {
int NZ_PHI2,
int NY_R,
const unsigned int* __restrict__ ID,
const $REAL* __restrict__ Ex, const $REAL* __restrict__ Ey, const $REAL* __restrict__ Ez,
const $REAL* __restrict__ Ex,
const $REAL* __restrict__ Ey,
const $REAL* __restrict__ Ez,
$REAL *Hx,
$REAL *Hy,
const $REAL* __restrict__ Hz,
$REAL *PHI1,
$REAL *PHI2,
const $REAL* __restrict__ RA, const $REAL* __restrict__ RB, const $REAL* __restrict__ RE, const $REAL* __restrict__ RF, $REAL d)
const $REAL* __restrict__ RA,
const $REAL* __restrict__ RB,
const $REAL* __restrict__ RE,
const $REAL* __restrict__ RF,
$REAL d)
"""
),
"opencl": Template(

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -126,10 +126,10 @@ update_magnetic_dipole = {
$REAL dx,
$REAL dy,
$REAL dz,
__global const int* restrict rcinfo1,
__global const $REAL* restrict rcinfo2,
__global const $REAL* restrict rcwaveforms,
__global const unsigned int* estrict ID,
__global const int* restrict srcinfo1,
__global const $REAL* restrict srcinfo2,
__global const $REAL* restrict srcwaveforms,
__global const unsigned int* restrict ID,
__global $REAL *Hx,
__global $REAL *Hy,
__global $REAL *Hz
@@ -208,10 +208,10 @@ update_voltage_source = {
$REAL dx,
$REAL dy,
$REAL dz,
__global const int* restrict rcinfo1,
__global const $REAL* restrict rcinfo2,
__global const $REAL* restrict rcwaveforms,
__global const unsigned int* estrict ID,
__global const int* restrict srcinfo1,
__global const $REAL* restrict srcinfo2,
__global const $REAL* restrict srcwaveforms,
__global const unsigned int* restrict ID,
__global $REAL *Ex,
__global $REAL *Ey,
__global $REAL *Ez

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -27,7 +27,7 @@ cpdef void generate_fractal2D(
int nx,
int ny,
int nthreads,
int b,
float D,
np.float64_t[:] weighting,
np.float64_t[:] v1,
np.complex128_t[:, ::1] A,
@@ -38,7 +38,7 @@ cpdef void generate_fractal2D(
Args:
nx, ny: int for fractal surface size in cells.
nthreads: int for number of threads to use
b: int for constant related to fractal dimension.
D: float for fractal dimension.
weighting: memoryview for access to weighting vector.
v1: memoryview for access to positional vector at centre of array,
scaled by weighting.
@@ -59,7 +59,7 @@ cpdef void generate_fractal2D(
# Calulate norm of v2 - v1
rr = ((v2x - v1[0])**2 + (v2y - v1[1])**2)**(1/2)
B = rr**b
B = rr**D
if B == 0:
B = 0.9
@@ -71,7 +71,7 @@ cpdef void generate_fractal3D(
int ny,
int nz,
int nthreads,
int b,
float D,
np.float64_t[:] weighting,
np.float64_t[:] v1,
np.complex128_t[:, :, ::1] A,
@@ -82,13 +82,13 @@ cpdef void generate_fractal3D(
Args:
nx, ny, nz: int for fractal volume size in cells.
nthreads: int for number of threads to use
b: int for constant related to fractal dimension.
D: float for fractal dimension.
weighting: memoryview for access to weighting vector.
v1: memoryview for access to positional vector at centre of array,
scaled by weighting.
A: memoryview for access to array containing random numbers
(to be convolved with fractal function).
fractalsurface: memoryview for access to array containing fractal
fractalvolume: memoryview for access to array containing fractal
volume data.
"""
@@ -105,7 +105,7 @@ cpdef void generate_fractal3D(
# Calulate norm of v2 - v1
rr = ((v2x - v1[0])**2 + (v2y - v1[1])**2 + (v2z - v1[2])**2)**(1/2)
B = rr**b
B = rr**D
if B == 0:
B = 0.9

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -21,7 +21,6 @@ import numpy as np
cimport numpy as np
np.seterr(divide='raise')
from cython.parallel import prange
from gprMax.cython.yee_cell_setget_rigid cimport (
set_rigid_E,
@@ -423,7 +422,7 @@ cpdef void build_triangle(
"""
cdef Py_ssize_t i, j, k
cdef int i1, i2, j1, j2, sign, level, thicknesscells
cdef int i1, i2, j1, j2, sign, levelcells, thicknesscells
cdef float area, s, t
# Calculate a bounding box for the triangle
@@ -433,7 +432,7 @@ cpdef void build_triangle(
i2 = round_value(np.amax([y1, y2, y3]) / dy) + 1
j1 = round_value(np.amin([z1, z2, z3]) / dz) - 1
j2 = round_value(np.amax([z1, z2, z3]) / dz) + 1
level = round_value(x1 / dx)
levelcells = round_value(x1 / dx)
thicknesscells = round_value(thickness / dx)
elif normal == 'y':
area = 0.5 * (-z2 * x3 + z1 * (-x2 + x3) + x1 * (z2 - z3) + x2 * z3)
@@ -441,7 +440,7 @@ cpdef void build_triangle(
i2 = round_value(np.amax([x1, x2, x3]) / dx) + 1
j1 = round_value(np.amin([z1, z2, z3]) / dz) - 1
j2 = round_value(np.amax([z1, z2, z3]) / dz) + 1
level = round_value(y1 /dy)
levelcells = round_value(y1 /dy)
thicknesscells = round_value(thickness / dy)
elif normal == 'z':
area = 0.5 * (-y2 * x3 + y1 * (-x2 + x3) + x1 * (y2 - y3) + x2 * y3)
@@ -449,7 +448,7 @@ cpdef void build_triangle(
i2 = round_value(np.amax([x1, x2, x3]) / dx) + 1
j1 = round_value(np.amin([y1, y2, y3]) / dy) - 1
j2 = round_value(np.amax([y1, y2, y3]) / dy) + 1
level = round_value(z1 / dz)
levelcells = round_value(z1 / dz)
thicknesscells = round_value(thickness / dz)
sign = np.sign(area)
@@ -479,16 +478,16 @@ cpdef void build_triangle(
if s > 0 and t > 0 and (s + t) < 2 * area * sign:
if thicknesscells == 0:
if normal == 'x':
build_face_yz(level, i, j, numIDy, numIDz,
build_face_yz(levelcells, i, j, numIDy, numIDz,
rigidE, rigidH, ID)
elif normal == 'y':
build_face_xz(i, level, j, numIDx, numIDz,
build_face_xz(i, levelcells, j, numIDx, numIDz,
rigidE, rigidH, ID)
elif normal == 'z':
build_face_xy(i, j, level, numIDx, numIDy,
build_face_xy(i, j, levelcells, numIDx, numIDy,
rigidE, rigidH, ID)
else:
for k in range(level, level + thicknesscells):
for k in range(levelcells, levelcells + thicknesscells):
if normal == 'x':
build_voxel(k, i, j, numID, numIDx, numIDy, numIDz,
averaging, solid, rigidE, rigidH, ID)
@@ -503,7 +502,7 @@ cpdef void build_triangle(
cpdef void build_cylindrical_sector(
float ctr1,
float ctr2,
int level,
float level,
float sectorstartangle,
float sectorangle,
float radius,
@@ -532,7 +531,7 @@ cpdef void build_cylindrical_sector(
Args:
ctr1, ctr2: floats for coordinates of centre of circle.
level: int for the third dimensional coordinate.
level: float for the third dimensional coordinate.
sectorstartangle: float for angle (in radians) of start of sector.
sectorangle: float for angle (in radians) that sector makes.
radius: float for radius of the cylindrical sector.
@@ -555,7 +554,8 @@ cpdef void build_cylindrical_sector(
y2 = round_value((ctr1 + radius)/dy)
z1 = round_value((ctr2 - radius)/dz)
z2 = round_value((ctr2 + radius)/dz)
thicknesscells = round_value(thickness/dx)
levelcells = round_value(level / dx)
thicknesscells = round_value(thickness / dx)
# Set bounds to domain if they outside
if y1 < 0:
@@ -572,10 +572,10 @@ cpdef void build_cylindrical_sector(
if is_inside_sector(y * dy + 0.5 * dy, z * dz + 0.5 * dz, ctr1,
ctr2, sectorstartangle, sectorangle, radius):
if thicknesscells == 0:
build_face_yz(level, y, z, numIDy, numIDz,
build_face_yz(levelcells, y, z, numIDy, numIDz,
rigidE, rigidH, ID)
else:
for x in range(level, level + thicknesscells):
for x in range(levelcells, levelcells + thicknesscells):
build_voxel(x, y, z, numID, numIDx, numIDy, numIDz,
averaging, solid, rigidE, rigidH, ID)
@@ -586,7 +586,8 @@ cpdef void build_cylindrical_sector(
x2 = round_value((ctr1 + radius)/dx)
z1 = round_value((ctr2 - radius)/dz)
z2 = round_value((ctr2 + radius)/dz)
thicknesscells = round_value(thickness/dy)
levelcells = round_value(level / dy)
thicknesscells = round_value(thickness / dy)
# Set bounds to domain if they outside
if x1 < 0:
@@ -603,10 +604,10 @@ cpdef void build_cylindrical_sector(
if is_inside_sector(x * dx + 0.5 * dx, z * dz + 0.5 * dz, ctr1,
ctr2, sectorstartangle, sectorangle, radius):
if thicknesscells == 0:
build_face_xz(x, level, z, numIDx, numIDz,
build_face_xz(x, levelcells, z, numIDx, numIDz,
rigidE, rigidH, ID)
else:
for y in range(level, level + thicknesscells):
for y in range(levelcells, levelcells + thicknesscells):
build_voxel(x, y, z, numID, numIDx, numIDy, numIDz,
averaging, solid, rigidE, rigidH, ID)
@@ -617,7 +618,8 @@ cpdef void build_cylindrical_sector(
x2 = round_value((ctr1 + radius)/dx)
y1 = round_value((ctr2 - radius)/dy)
y2 = round_value((ctr2 + radius)/dy)
thicknesscells = round_value(thickness/dz)
levelcells = round_value(level / dz)
thicknesscells = round_value(thickness / dz)
# Set bounds to domain if they outside
if x1 < 0:
@@ -634,10 +636,10 @@ cpdef void build_cylindrical_sector(
if is_inside_sector(x * dx + 0.5 * dx, y * dy + 0.5 * dy, ctr1,
ctr2, sectorstartangle, sectorangle, radius):
if thicknesscells == 0:
build_face_xy(x, y, level, numIDx, numIDy,
build_face_xy(x, y, levelcells, numIDx, numIDy,
rigidE, rigidH, ID)
else:
for z in range(level, level + thicknesscells):
for z in range(levelcells, levelcells + thicknesscells):
build_voxel(x, y, z, numID, numIDx, numIDy, numIDz,
averaging, solid, rigidE, rigidH, ID)
@@ -649,7 +651,6 @@ cpdef void build_box(
int yf,
int zs,
int zf,
int nthreads,
int numID,
int numIDx,
int numIDy,
@@ -664,7 +665,6 @@ cpdef void build_box(
Args:
xs, xf, ys, yf, zs, zf: ints for cell coordinates of entire box.
nthreads: int for number of threads to use
numID, numIDx, numIDy, numIDz: ints for numeric ID of material.
averaging: bint for whether material property averaging will occur for
the object.
@@ -674,14 +674,14 @@ cpdef void build_box(
cdef Py_ssize_t i, j, k
if averaging:
for i in prange(xs, xf, nogil=True, schedule='static', num_threads=nthreads):
for i in range(xs, xf):
for j in range(ys, yf):
for k in range(zs, zf):
solid[i, j, k] = numID
unset_rigid_E(i, j, k, rigidE)
unset_rigid_H(i, j, k, rigidH)
else:
for i in prange(xs, xf, nogil=True, schedule='static', num_threads=nthreads):
for i in range(xs, xf):
for j in range(ys, yf):
for k in range(zs, zf):
solid[i, j, k] = numID
@@ -694,32 +694,32 @@ cpdef void build_box(
ID[4, i, j, k] = numIDy
ID[5, i, j, k] = numIDz
for i in prange(xs, xf, nogil=True, schedule='static', num_threads=nthreads):
for i in range(xs, xf):
j = yf
k = zf
ID[0, i, j, k] = numIDx
i = xf
for j in prange(ys, yf, nogil=True, schedule='static', num_threads=nthreads):
for j in range(ys, yf):
for k in range(zf, zf + 1):
ID[1, i, j, k] = numIDy
i = xf
j = yf
for k in prange(zs, zf, nogil=True, schedule='static', num_threads=nthreads):
for k in range(zs, zf):
ID[2, i, j, k] = numIDz
i = xf
for j in prange(ys, yf, nogil=True, schedule='static', num_threads=nthreads):
for j in range(ys, yf):
for k in range(zs, zf):
ID[3, i, j, k] = numIDx
for i in prange(xs, xf, nogil=True, schedule='static', num_threads=nthreads):
for i in range(xs, xf):
j = yf
for k in range(zs, zf):
ID[4, i, j, k] = numIDy
for i in prange(xs, xf, nogil=True, schedule='static', num_threads=nthreads):
for i in range(xs, xf):
for j in range(ys, yf):
k = zf
ID[5, i, j, k] = numIDz
@@ -1272,7 +1272,6 @@ cpdef void build_voxels_from_array(
int xs,
int ys,
int zs,
int nthreads,
int numexistmaterials,
bint averaging,
np.int16_t[:, :, ::1] data,
@@ -1286,7 +1285,6 @@ cpdef void build_voxels_from_array(
Args:
xs, ys, zs: ints for cell coordinates of position of start of array in
domain.
nthreads: int for number of threads to use
numexistmaterials: int for number of existing materials in model prior
to building voxels.
averaging: bint for whether material property averaging will occur for
@@ -1320,7 +1318,7 @@ cpdef void build_voxels_from_array(
else:
zf = zs + data.shape[2]
for i in prange(xs, xf, nogil=True, schedule='static', num_threads=nthreads):
for i in range(xs, xf):
for j in range(ys, yf):
for k in range(zs, zf):
numID = data[i - xs, j - ys, k - zs]
@@ -1333,7 +1331,6 @@ cpdef void build_voxels_from_array_mask(
int xs,
int ys,
int zs,
int nthreads,
int waternumID,
int grassnumID,
bint averaging,
@@ -1348,7 +1345,6 @@ cpdef void build_voxels_from_array_mask(
Args:
xs, ys, zs: ints for cell coordinates of position of start of array in domain.
nthreads: int for number of threads to use
waternumID, grassnumID: ints for numeric ID of water and grass materials.
averaging: bint for whether material property averaging will occur for
the object.
@@ -1365,7 +1361,7 @@ cpdef void build_voxels_from_array_mask(
yf = ys + data.shape[1]
zf = zs + data.shape[2]
for i in prange(xs, xf, nogil=True, schedule='static', num_threads=nthreads):
for i in range(xs, xf):
for j in range(ys, yf):
for k in range(zs, zf):
if mask[i - xs, j - ys, k - zs] == 1:

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -32,6 +32,7 @@ def store_outputs(G):
G: FDTDGrid class describing a grid in a model.
"""
# Assign iteration and fields to local variables
iteration = G.iteration
Ex, Ey, Ez, Hx, Hy, Hz = G.Ex, G.Ey, G.Ez, G.Hx, G.Hy, G.Hz
@@ -44,7 +45,9 @@ def store_outputs(G):
# Store current component
else:
func = globals()[output]
rx.outputs[output][iteration] = func(rx.xcoord, rx.ycoord, rx.zcoord, Hx, Hy, Hz, G)
rx.outputs[output][iteration] = func(
rx.xcoord, rx.ycoord, rx.zcoord, Hx, Hy, Hz, G
)
for tl in G.transmissionlines:
tl.Vtotal[iteration] = tl.voltage[tl.antpos]
@@ -59,70 +62,78 @@ def write_hdf5_outputfile(outputfile, G):
G: FDTDGrid class describing a grid in a model.
"""
# Check for any receivers in subgrids
sg_rxs = [True for sg in G.subgrids if sg.rxs]
# Create output file and write top-level meta data
if G.rxs or sg_rxs:
f = h5py.File(outputfile, "w")
f.attrs["gprMax"] = __version__
f.attrs["Title"] = G.title
# Write meta data and data for main grid
if G.rxs:
write_hd5_data(f, G)
# Create output file and write top-level meta data, meta data for main grid,
# and any outputs in the main grid
f = h5py.File(outputfile, "w")
f.attrs["gprMax"] = __version__
f.attrs["Title"] = G.title
write_hd5_data(f, G)
# Write meta data and data for any subgrids
if sg_rxs:
sg_rxs = [True for sg in G.subgrids if sg.rxs]
sg_tls = [True for sg in G.subgrids if sg.transmissionlines]
if sg_rxs or sg_tls:
for sg in G.subgrids:
grp = f.create_group(f"/subgrids/{sg.name}")
write_hd5_data(grp, sg, is_subgrid=True)
if G.rxs or sg_rxs:
logger.basic(f"Written output file: {outputfile.name}")
logger.basic(f"Written output file: {outputfile.name}")
def write_hd5_data(basegrp, G, is_subgrid=False):
def write_hd5_data(basegrp, grid, is_subgrid=False):
"""Writes grid meta data and data to HDF5 group.
Args:
basegrp: dict of HDF5 group.
G: FDTDGrid class describing a grid in a model.
grid: FDTDGrid class describing a grid in a model.
is_subgrid: boolean for grid instance the main grid or a subgrid.
"""
# Write meta data for grid
basegrp.attrs["Iterations"] = G.iterations
basegrp.attrs["nx_ny_nz"] = (G.nx, G.ny, G.nz)
basegrp.attrs["dx_dy_dz"] = (G.dx, G.dy, G.dz)
basegrp.attrs["dt"] = G.dt
nsrc = len(G.voltagesources + G.hertziandipoles + G.magneticdipoles + G.transmissionlines)
basegrp.attrs["Iterations"] = grid.iterations
basegrp.attrs["nx_ny_nz"] = (grid.nx, grid.ny, grid.nz)
basegrp.attrs["dx_dy_dz"] = (grid.dx, grid.dy, grid.dz)
basegrp.attrs["dt"] = grid.dt
nsrc = len(
grid.voltagesources
+ grid.hertziandipoles
+ grid.magneticdipoles
+ grid.transmissionlines
)
basegrp.attrs["nsrc"] = nsrc
basegrp.attrs["nrx"] = len(G.rxs)
basegrp.attrs["srcsteps"] = G.srcsteps
basegrp.attrs["rxsteps"] = G.rxsteps
basegrp.attrs["nrx"] = len(grid.rxs)
basegrp.attrs["srcsteps"] = grid.srcsteps
basegrp.attrs["rxsteps"] = grid.rxsteps
if is_subgrid:
# Write additional meta data about subgrid
basegrp.attrs["is_os_sep"] = G.is_os_sep
basegrp.attrs["pml_separation"] = G.pml_separation
basegrp.attrs["subgrid_pml_thickness"] = G.pml["thickness"]["x0"]
basegrp.attrs["filter"] = G.filter
basegrp.attrs["ratio"] = G.ratio
basegrp.attrs["interpolation"] = G.interpolation
basegrp.attrs["is_os_sep"] = grid.is_os_sep
basegrp.attrs["pml_separation"] = grid.pml_separation
basegrp.attrs["subgrid_pml_thickness"] = grid.pmls["thickness"]["x0"]
basegrp.attrs["filter"] = grid.filter
basegrp.attrs["ratio"] = grid.ratio
basegrp.attrs["interpolation"] = grid.interpolation
# Create group for sources (except transmission lines); add type and positional data attributes
srclist = G.voltagesources + G.hertziandipoles + G.magneticdipoles
srclist = grid.voltagesources + grid.hertziandipoles + grid.magneticdipoles
for srcindex, src in enumerate(srclist):
grp = basegrp.create_group(f"srcs/src{str(srcindex + 1)}")
grp.attrs["Type"] = type(src).__name__
grp.attrs["Position"] = (src.xcoord * G.dx, src.ycoord * G.dy, src.zcoord * G.dz)
grp.attrs["Position"] = (
src.xcoord * grid.dx,
src.ycoord * grid.dy,
src.zcoord * grid.dz,
)
# Create group for transmission lines; add positional data, line resistance and
# line discretisation attributes; write arrays for line voltages and currents
for tlindex, tl in enumerate(G.transmissionlines):
for tlindex, tl in enumerate(grid.transmissionlines):
grp = basegrp.create_group("tls/tl" + str(tlindex + 1))
grp.attrs["Position"] = (tl.xcoord * G.dx, tl.ycoord * G.dy, tl.zcoord * G.dz)
grp.attrs["Position"] = (
tl.xcoord * grid.dx,
tl.ycoord * grid.dy,
tl.zcoord * grid.dz,
)
grp.attrs["Resistance"] = tl.resistance
grp.attrs["dl"] = tl.dl
# Save incident voltage and current
@@ -133,11 +144,15 @@ def write_hd5_data(basegrp, G, is_subgrid=False):
basegrp["tls/tl" + str(tlindex + 1) + "/Itotal"] = tl.Itotal
# Create group, add positional data and write field component arrays for receivers
for rxindex, rx in enumerate(G.rxs):
for rxindex, rx in enumerate(grid.rxs):
grp = basegrp.create_group("rxs/rx" + str(rxindex + 1))
if rx.ID:
grp.attrs["Name"] = rx.ID
grp.attrs["Position"] = (rx.xcoord * G.dx, rx.ycoord * G.dy, rx.zcoord * G.dz)
grp.attrs["Position"] = (
rx.xcoord * grid.dx,
rx.ycoord * grid.dy,
rx.zcoord * grid.dz,
)
for output in rx.outputs:
basegrp["rxs/rx" + str(rxindex + 1) + "/" + output] = rx.outputs[output]
@@ -155,7 +170,9 @@ def Ix(x, y, z, Hx, Hy, Hz, G):
if y == 0 or z == 0:
Ix = 0
else:
Ix = G.dy * (Hy[x, y, z - 1] - Hy[x, y, z]) + G.dz * (Hz[x, y, z] - Hz[x, y - 1, z])
Ix = G.dy * (Hy[x, y, z - 1] - Hy[x, y, z]) + G.dz * (
Hz[x, y, z] - Hz[x, y - 1, z]
)
return Ix
@@ -172,7 +189,9 @@ def Iy(x, y, z, Hx, Hy, Hz, G):
if x == 0 or z == 0:
Iy = 0
else:
Iy = G.dx * (Hx[x, y, z] - Hx[x, y, z - 1]) + G.dz * (Hz[x - 1, y, z] - Hz[x, y, z])
Iy = G.dx * (Hx[x, y, z] - Hx[x, y, z - 1]) + G.dz * (
Hz[x - 1, y, z] - Hz[x, y, z]
)
return Iy
@@ -189,6 +208,8 @@ def Iz(x, y, z, Hx, Hy, Hz, G):
if x == 0 or y == 0:
Iz = 0
else:
Iz = G.dx * (Hx[x, y - 1, z] - Hx[x, y, z]) + G.dy * (Hy[x, y, z] - Hy[x - 1, y, z])
Iz = G.dx * (Hx[x, y - 1, z] - Hx[x, y, z]) + G.dy * (
Hy[x, y, z] - Hy[x - 1, y, z]
)
return Iz

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -32,7 +32,7 @@ class FractalSurface:
surfaceIDs = ["xminus", "xplus", "yminus", "yplus", "zminus", "zplus"]
def __init__(self, xs, xf, ys, yf, zs, zf, dimension):
def __init__(self, xs, xf, ys, yf, zs, zf, dimension, seed):
"""
Args:
xs, xf, ys, yf, zs, zf: floats for the extent of the fractal surface
@@ -40,6 +40,7 @@ class FractalSurface:
to correctly define a surface).
dimension: float for the fractal dimension that controls the fractal
distribution.
seed: int for seed value for random number generator.
"""
self.ID = None
@@ -54,10 +55,8 @@ class FractalSurface:
self.ny = yf - ys
self.nz = zf - zs
self.dtype = np.dtype(np.complex128)
self.seed = None
self.dimension = dimension
# Constant related to fractal dimension from: http://dx.doi.org/10.1017/CBO9781139174695
self.b = -(2 * self.dimension - 7) / 2
self.seed = seed
self.dimension = dimension # Fractal dimension from: http://dx.doi.org/10.1017/CBO9781139174695
self.weighting = np.array([1, 1], dtype=np.float64)
self.fractalrange = (0, 0)
self.filldepth = 0
@@ -83,7 +82,12 @@ class FractalSurface:
self.fractalsurface = np.zeros(surfacedims, dtype=self.dtype)
# Positional vector at centre of array, scaled by weighting
v1 = np.array([self.weighting[0] * (surfacedims[0]) / 2, self.weighting[1] * (surfacedims[1]) / 2])
v1 = np.array(
[
self.weighting[0] * (surfacedims[0]) / 2,
self.weighting[1] * (surfacedims[1]) / 2,
]
)
# 2D array of random numbers to be convolved with the fractal function
rng = np.random.default_rng(seed=self.seed)
@@ -99,7 +103,7 @@ class FractalSurface:
surfacedims[0],
surfacedims[1],
config.get_model_config().ompthreads,
self.b,
self.dimension,
self.weighting,
v1,
A,
@@ -120,21 +124,24 @@ class FractalSurface:
fractalmax = np.amax(self.fractalsurface)
fractalrange = fractalmax - fractalmin
self.fractalsurface = (
self.fractalsurface * ((self.fractalrange[1] - self.fractalrange[0]) / fractalrange)
self.fractalsurface
* ((self.fractalrange[1] - self.fractalrange[0]) / fractalrange)
+ self.fractalrange[0]
- ((self.fractalrange[1] - self.fractalrange[0]) / fractalrange) * fractalmin
- ((self.fractalrange[1] - self.fractalrange[0]) / fractalrange)
* fractalmin
)
class FractalVolume:
"""Fractal volumes."""
def __init__(self, xs, xf, ys, yf, zs, zf, dimension):
def __init__(self, xs, xf, ys, yf, zs, zf, dimension, seed):
"""
Args:
xs, xf, ys, yf, zs, zf: floats for the extent of the fractal volume.
dimension: float for the fractal dimension that controls the fractal
distribution.
seed: int for seed value for random number generator.
"""
self.ID = None
@@ -156,10 +163,8 @@ class FractalVolume:
self.originalzf = zf
self.averaging = False
self.dtype = np.dtype(np.complex128)
self.seed = None
self.dimension = dimension
# Constant related to fractal dimension from: http://dx.doi.org/10.1017/CBO9781139174695
self.b = -(2 * self.dimension - 7) / 2
self.seed = seed
self.dimension = dimension # Fractal dimension from: http://dx.doi.org/10.1017/CBO9781139174695
self.weighting = np.array([1, 1, 1], dtype=np.float64)
self.nbins = 0
self.fractalsurfaces = []
@@ -169,16 +174,24 @@ class FractalVolume:
# Scale filter according to size of fractal volume
if self.nx == 1:
filterscaling = np.amin(np.array([self.ny, self.nz])) / np.array([self.ny, self.nz])
filterscaling = np.amin(np.array([self.ny, self.nz])) / np.array(
[self.ny, self.nz]
)
filterscaling = np.insert(filterscaling, 0, 1)
elif self.ny == 1:
filterscaling = np.amin(np.array([self.nx, self.nz])) / np.array([self.nx, self.nz])
filterscaling = np.amin(np.array([self.nx, self.nz])) / np.array(
[self.nx, self.nz]
)
filterscaling = np.insert(filterscaling, 1, 1)
elif self.nz == 1:
filterscaling = np.amin(np.array([self.nx, self.ny])) / np.array([self.nx, self.ny])
filterscaling = np.amin(np.array([self.nx, self.ny])) / np.array(
[self.nx, self.ny]
)
filterscaling = np.insert(filterscaling, 2, 1)
else:
filterscaling = np.amin(np.array([self.nx, self.ny, self.nz])) / np.array([self.nx, self.ny, self.nz])
filterscaling = np.amin(np.array([self.nx, self.ny, self.nz])) / np.array(
[self.nx, self.ny, self.nz]
)
# Adjust weighting to account for filter scaling
self.weighting = np.multiply(self.weighting, filterscaling)
@@ -187,7 +200,11 @@ class FractalVolume:
# Positional vector at centre of array, scaled by weighting
v1 = np.array(
[self.weighting[0] * self.nx / 2, self.weighting[1] * self.ny / 2, self.weighting[2] * self.nz / 2]
[
self.weighting[0] * self.nx / 2,
self.weighting[1] * self.ny / 2,
self.weighting[2] * self.nz / 2,
]
)
# 3D array of random numbers to be convolved with the fractal function
@@ -205,7 +222,7 @@ class FractalVolume:
self.ny,
self.nz,
config.get_model_config().ompthreads,
self.b,
self.dimension,
self.weighting,
v1,
A,
@@ -224,10 +241,14 @@ class FractalVolume:
)
# Bin fractal values
bins = np.linspace(np.amin(self.fractalvolume), np.amax(self.fractalvolume), self.nbins)
bins = np.linspace(
np.amin(self.fractalvolume), np.amax(self.fractalvolume), self.nbins
)
for j in range(self.ny):
for k in range(self.nz):
self.fractalvolume[:, j, k] = np.digitize(self.fractalvolume[:, j, k], bins, right=True)
self.fractalvolume[:, j, k] = np.digitize(
self.fractalvolume[:, j, k], bins, right=True
)
def generate_volume_mask(self):
"""Generate a 3D volume to use as a mask for adding rough surfaces,
@@ -248,17 +269,25 @@ class FractalVolume:
class Grass:
"""Geometry information for blades of grass."""
def __init__(self, numblades):
def __init__(self, numblades, seed):
"""
Args:
numblades: int for the number of blades of grass.
seed: int for seed value for random number generator.
"""
self.numblades = numblades
self.geometryparams = np.zeros((self.numblades, 6), dtype=config.sim_config.dtypes["float_or_double"])
self.seed = None
self.geometryparams = np.zeros(
(self.numblades, 6), dtype=config.sim_config.dtypes["float_or_double"]
)
self.seed = seed
self.set_geometry_parameters()
def set_geometry_parameters(self):
"""Sets randomly defined parameters that will be used to calculate
blade and root geometries.
"""
# Randomly defined parameters that will be used to calculate geometry
self.R1 = np.random.default_rng(seed=self.seed)
self.R2 = np.random.default_rng(seed=self.seed)
self.R3 = np.random.default_rng(seed=self.seed)

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -32,7 +32,7 @@ import gprMax.config as config
from ._version import __version__
from .cython.geometry_outputs import write_lines
from .subgrids.grid import SubGridBaseGrid
from .utilities.utilities import get_terminal_width, numeric_list_to_float_list, numeric_list_to_int_list
from .utilities.utilities import get_terminal_width
logger = logging.getLogger(__name__)
@@ -52,7 +52,8 @@ def save_geometry_views(gvs):
total=gv.nbytes,
unit="byte",
unit_scale=True,
desc=f"Writing geometry view file {i + 1}/{len(gvs)}, " f"{gv.filename.name}{gv.vtkfiletype.ext}",
desc=f"Writing geometry view file {i + 1}/{len(gvs)}, "
f"{gv.filename.name}{gv.vtkfiletype.ext}",
ncols=get_terminal_width() - 1,
file=sys.stdout,
disable=not config.sim_config.general["progressbars"],
@@ -89,13 +90,16 @@ class GeometryView:
self.dy = dy
self.dz = dz
self.filename = filename
self.filenamebase = filename
self.grid = grid
self.nbytes = None
def set_filename(self):
"""Constructs filename from user-supplied name and model run number."""
parts = config.get_model_config().output_file_path.parts
self.filename = Path(*parts[:-1], self.filename + config.get_model_config().appendmodelnumber)
self.filename = Path(
*parts[:-1], self.filenamebase + config.get_model_config().appendmodelnumber
)
class GeometryViewLines(GeometryView):
@@ -121,7 +125,12 @@ class GeometryViewLines(GeometryView):
):
# Require contiguous for evtk library
ID = np.ascontiguousarray(
self.grid.ID[:, self.xs : self.xf : self.dx, self.ys : self.yf : self.dy, self.zs : self.zf : self.dz]
self.grid.ID[
:,
self.xs : self.xf : self.dx,
self.ys : self.yf : self.dy,
self.zs : self.zf : self.dz,
]
)
else:
# This array is contiguous by design
@@ -157,7 +166,15 @@ class GeometryViewLines(GeometryView):
offsets_size = np.arange(start=2, step=2, stop=len(x) + 1, dtype="int32").nbytes
connect_size = len(x) * np.dtype("int32").itemsize
cell_type_size = len(x) * np.dtype("uint8").itemsize
self.nbytes = x.nbytes + y.nbytes + z.nbytes + lines.nbytes + offsets_size + connect_size + cell_type_size
self.nbytes = (
x.nbytes
+ y.nbytes
+ z.nbytes
+ lines.nbytes
+ offsets_size
+ connect_size
+ cell_type_size
)
vtk_data = {"x": x, "y": y, "z": z, "data": lines, "comments": comments}
@@ -204,7 +221,11 @@ class GeometryViewVoxels(GeometryView):
):
# Require contiguous for evtk library
solid = np.ascontiguousarray(
self.grid.solid[self.xs : self.xf : self.dx, self.ys : self.yf : self.dy, self.zs : self.zf : self.dz]
self.grid.solid[
self.xs : self.xf : self.dx,
self.ys : self.yf : self.dy,
self.zs : self.zf : self.dz,
]
)
else:
# This array is contiguous by design
@@ -235,13 +256,21 @@ class GeometryViewVoxels(GeometryView):
(self.grid.k0 * self.grid.dz * self.grid.ratio),
)
else:
origin = ((self.xs * self.grid.dx), (self.ys * self.grid.dy), (self.zs * self.grid.dz))
origin = (
(self.xs * self.grid.dx),
(self.ys * self.grid.dy),
(self.zs * self.grid.dz),
)
# Write the VTK file .vti
imageToVTK(
str(self.filename),
origin=origin,
spacing=((self.dx * self.grid.dx), (self.dy * self.grid.dy), (self.dz * self.grid.dz)),
spacing=(
(self.dx * self.grid.dx),
(self.dy * self.grid.dy),
(self.dz * self.grid.dz),
),
cellData={"Material": vtk_data["data"]},
comments=[vtk_data["comments"]],
)
@@ -303,11 +332,17 @@ class Comments:
if grid.pmls["thickness"]["z0"] - self.gv.zs > 0:
pmlstorender["z0"] = int(grid.pmls["thickness"]["z0"] - self.gv.zs)
if self.gv.xf > grid.nx - grid.pmls["thickness"]["xmax"]:
pmlstorender["xmax"] = int(self.gv.xf - (grid.nx - grid.pmls["thickness"]["xmax"]))
pmlstorender["xmax"] = int(
self.gv.xf - (grid.nx - grid.pmls["thickness"]["xmax"])
)
if self.gv.yf > grid.ny - grid.pmls["thickness"]["ymax"]:
pmlstorender["ymax"] = int(self.gv.yf - (grid.ny - grid.pmls["thickness"]["ymax"]))
pmlstorender["ymax"] = int(
self.gv.yf - (grid.ny - grid.pmls["thickness"]["ymax"])
)
if self.gv.zf > grid.nz - grid.pmls["thickness"]["zmax"]:
pmlstorender["zmax"] = int(self.gv.zf - (grid.nz - grid.pmls["thickness"]["zmax"]))
pmlstorender["zmax"] = int(
self.gv.zf - (grid.nz - grid.pmls["thickness"]["zmax"])
)
return list(pmlstorender.values())
@@ -315,8 +350,12 @@ class Comments:
"""Used to name sources and/or receivers."""
sc = []
for src in srcs:
p = (src.xcoord * self.grid.dx, src.ycoord * self.grid.dy, src.zcoord * self.grid.dz)
p = numeric_list_to_float_list(p)
p = (
src.xcoord * self.grid.dx,
src.ycoord * self.grid.dy,
src.zcoord * self.grid.dz,
)
p = list(map(float, p))
s = {"name": src.ID, "position": p}
sc.append(s)
@@ -324,14 +363,16 @@ class Comments:
return sc
def dx_dy_dz_comment(self):
return numeric_list_to_float_list([self.grid.dx, self.grid.dy, self.grid.dz])
return list(map(float, [self.grid.dx, self.grid.dy, self.grid.dz]))
def nx_ny_nz_comment(self):
return numeric_list_to_int_list([self.grid.nx, self.grid.ny, self.grid.nz])
return list(map(int, [self.grid.nx, self.grid.ny, self.grid.nz]))
def materials_comment(self):
if not self.averaged_materials:
return [m.ID for m in self.grid.materials if m.type != "dielectric-smoothed"]
return [
m.ID for m in self.grid.materials if m.type != "dielectric-smoothed"
]
else:
return [m.ID for m in self.grid.materials]
@@ -339,7 +380,9 @@ class Comments:
class GeometryObjects:
"""Geometry objects to be written to file."""
def __init__(self, xs=None, ys=None, zs=None, xf=None, yf=None, zf=None, basefilename=None):
def __init__(
self, xs=None, ys=None, zs=None, xf=None, yf=None, zf=None, basefilename=None
):
"""
Args:
xs, xf, ys, yf, zs, zf: ints for extent of the volume in cells.
@@ -365,9 +408,23 @@ class GeometryObjects:
self.filename_materials = self.filename_materials.with_suffix(".txt")
# Sizes of arrays to write necessary to update progress bar
self.solidsize = (self.nx + 1) * (self.ny + 1) * (self.nz + 1) * np.dtype(np.uint32).itemsize
self.rigidsize = 18 * (self.nx + 1) * (self.ny + 1) * (self.nz + 1) * np.dtype(np.int8).itemsize
self.IDsize = 6 * (self.nx + 1) * (self.ny + 1) * (self.nz + 1) * np.dtype(np.uint32).itemsize
self.solidsize = (
(self.nx + 1) * (self.ny + 1) * (self.nz + 1) * np.dtype(np.uint32).itemsize
)
self.rigidsize = (
18
* (self.nx + 1)
* (self.ny + 1)
* (self.nz + 1)
* np.dtype(np.int8).itemsize
)
self.IDsize = (
6
* (self.nx + 1)
* (self.ny + 1)
* (self.nz + 1)
* np.dtype(np.uint32).itemsize
)
self.datawritesize = self.solidsize + self.rigidsize + self.IDsize
def write_hdf5(self, G, pbar):
@@ -384,16 +441,45 @@ class GeometryObjects:
fdata.attrs["dx_dy_dz"] = (G.dx, G.dy, G.dz)
# Get minimum and maximum integers of materials in geometry objects volume
minmat = np.amin(G.ID[:, self.xs : self.xf + 1, self.ys : self.yf + 1, self.zs : self.zf + 1])
maxmat = np.amax(G.ID[:, self.xs : self.xf + 1, self.ys : self.yf + 1, self.zs : self.zf + 1])
minmat = np.amin(
G.ID[
:,
self.xs : self.xf + 1,
self.ys : self.yf + 1,
self.zs : self.zf + 1,
]
)
maxmat = np.amax(
G.ID[
:,
self.xs : self.xf + 1,
self.ys : self.yf + 1,
self.zs : self.zf + 1,
]
)
fdata["/data"] = (
G.solid[self.xs : self.xf + 1, self.ys : self.yf + 1, self.zs : self.zf + 1].astype("int16") - minmat
G.solid[
self.xs : self.xf + 1, self.ys : self.yf + 1, self.zs : self.zf + 1
].astype("int16")
- minmat
)
pbar.update(self.solidsize)
fdata["/rigidE"] = G.rigidE[:, self.xs : self.xf + 1, self.ys : self.yf + 1, self.zs : self.zf + 1]
fdata["/rigidH"] = G.rigidH[:, self.xs : self.xf + 1, self.ys : self.yf + 1, self.zs : self.zf + 1]
fdata["/rigidE"] = G.rigidE[
:, self.xs : self.xf + 1, self.ys : self.yf + 1, self.zs : self.zf + 1
]
fdata["/rigidH"] = G.rigidH[
:, self.xs : self.xf + 1, self.ys : self.yf + 1, self.zs : self.zf + 1
]
pbar.update(self.rigidsize)
fdata["/ID"] = G.ID[:, self.xs : self.xf + 1, self.ys : self.yf + 1, self.zs : self.zf + 1] - minmat
fdata["/ID"] = (
G.ID[
:,
self.xs : self.xf + 1,
self.ys : self.yf + 1,
self.zs : self.zf + 1,
]
- minmat
)
pbar.update(self.IDsize)
# Write materials list to a text file
@@ -408,11 +494,18 @@ class GeometryObjects:
)
if hasattr(material, "poles"):
if "debye" in material.type:
dispersionstr = "#add_dispersion_debye: " f"{material.poles:g} "
dispersionstr = (
f"#add_dispersion_debye: {material.poles:g} "
)
for pole in range(material.poles):
dispersionstr += f"{material.deltaer[pole]:g} " f"{material.tau[pole]:g} "
dispersionstr += (
f"{material.deltaer[pole]:g} "
f"{material.tau[pole]:g} "
)
elif "lorenz" in material.type:
dispersionstr = f"#add_dispersion_lorenz: " f"{material.poles:g} "
dispersionstr = (
f"#add_dispersion_lorenz: {material.poles:g} "
)
for pole in range(material.poles):
dispersionstr += (
f"{material.deltaer[pole]:g} "
@@ -420,8 +513,13 @@ class GeometryObjects:
f"{material.alpha[pole]:g} "
)
elif "drude" in material.type:
dispersionstr = f"#add_dispersion_drude: " f"{material.poles:g} "
dispersionstr = (
f"#add_dispersion_drude: {material.poles:g} "
)
for pole in range(material.poles):
dispersionstr += f"{material.tau[pole]:g} " f"{material.alpha[pole]:g} "
dispersionstr += (
f"{material.tau[pole]:g} "
f"{material.alpha[pole]:g} "
)
dispersionstr += material.ID
fmaterials.write(dispersionstr + "\n")

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -25,7 +25,7 @@ from .utilities.logging import logging_config
# Arguments (used for API) and their default values (used for API and CLI)
args_defaults = {
"scenes": None,
"scenes": [],
"inputfile": None,
"outputfile": None,
"n": 1,
@@ -44,37 +44,39 @@ args_defaults = {
# Argument help messages (used for CLI argparse)
help_msg = {
"scenes": "(list, req): Scenes to run the model. "
"Multiple scene objects can given in order to run multiple "
"simulation runs. Each scene must contain the essential "
"simulation objects",
"inputfile": "(str, opt): Input file path. Can also run simulation " "by providing an input file.",
"scenes": "(list, req): Scenes to run the model. Multiple scene objects "
"can given in order to run multiple simulation runs. Each scene "
"must contain the essential simulation objects",
"inputfile": "(str, opt): Input file path. Can also run simulation by "
"providing an input file.",
"outputfile": "(str, req): File path to the output data file.",
"n": "(int, req): Number of required simulation runs.",
"i": "(int, opt): Model number to start/restart simulation "
"from. It would typically be used to restart a series of "
"models from a specific model number, with the n argument, "
"e.g. to restart from A-scan 45 when creating a B-scan "
"with 60 traces.",
"mpi": "(bool, opt): Flag to use Message Passing Interface (MPI) "
"task farm. This option is most usefully combined with n to "
"allow individual models to be farmed out using a MPI task "
"farm, e.g. to create a B-scan with 60 traces and use MPI to "
"farm out each trace. For further details see the parallel "
"performance section of the User Guide.",
"gpu": "(list/bool, opt): Flag to use NVIDIA GPU or list of NVIDIA " "GPU device ID(s) for specific GPU card(s).",
"opencl": "(list/bool, opt): Flag to use OpenCL or list of OpenCL " "device ID(s) for specific compute device(s).",
"i": "(int, opt): Model number to start/restart simulation from. It would "
"typically be used to restart a series of models from a specific "
"model number, with the n argument, e.g. to restart from A-scan 45 "
"when creating a B-scan with 60 traces.",
"mpi": "(bool, opt): Flag to use Message Passing Interface (MPI) task farm. "
"This option is most usefully combined with n to allow individual "
"models to be farmed out using a MPI task farm, e.g. to create a "
"B-scan with 60 traces and use MPI to farm out each trace. For "
"further details see the performance section of the User Guide.",
"gpu": "(list/bool, opt): Flag to use NVIDIA GPU or list of NVIDIA GPU "
"device ID(s) for specific GPU card(s).",
"opencl": "(list/bool, opt): Flag to use OpenCL or list of OpenCL device "
"ID(s) for specific compute device(s).",
"subgrid": "(bool, opt): Flag to use sub-gridding.",
"autotranslate": "(bool, opt): For sub-gridding - auto translate "
"objects with main grid coordinates to their "
"equivalent local grid coordinate within the "
"subgrid. If this option is off users must specify "
"sub-grid object point within the global subgrid space.",
"geometry_only": "(bool, opt): Build a model and produce any " "geometry views but do not run the simulation.",
"geometry_fixed": "(bool, opt): Run a series of models where the " "geometry does not change between models.",
"write_processed": "(bool, opt): Writes another input file after "
"any Python code (#python blocks) and in the "
"original input file has been processed.",
"autotranslate": "(bool, opt): For sub-gridding - auto translate objects "
"with main grid coordinates to their equivalent local "
"grid coordinate within the subgrid. If this option is "
"off users must specify sub-grid object point within the "
"global subgrid space.",
"geometry_only": "(bool, opt): Build a model and produce any geometry "
"views but do not run the simulation.",
"geometry_fixed": "(bool, opt): Run a series of models where the geometry "
"does not change between models.",
"write_processed": "(bool, opt): Writes another input file after any "
"Python code (#python blocks) and in the original input "
"file has been processed.",
"log_level": "(int, opt): Level of logging to use.",
"log_file": "(bool, opt): Write logging information to file.",
}
@@ -166,15 +168,26 @@ def cli():
"""Entry point for command line interface (CLI)."""
# Parse command line arguments
parser = argparse.ArgumentParser(prog="gprMax", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser = argparse.ArgumentParser(
prog="gprMax", formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument("inputfile", help=help_msg["inputfile"])
parser.add_argument("-n", default=args_defaults["n"], type=int, help=help_msg["n"])
parser.add_argument("-i", type=int, help=help_msg["i"])
parser.add_argument("-mpi", action="store_true", default=args_defaults["mpi"], help=help_msg["mpi"])
parser.add_argument("-gpu", type=int, action="append", nargs="*", help=help_msg["gpu"])
parser.add_argument("-opencl", type=int, action="append", nargs="*", help=help_msg["opencl"])
parser.add_argument(
"--geometry-only", action="store_true", default=args_defaults["geometry_only"], help=help_msg["geometry_only"]
"-mpi", action="store_true", default=args_defaults["mpi"], help=help_msg["mpi"]
)
parser.add_argument(
"-gpu", type=int, action="append", nargs="*", help=help_msg["gpu"]
)
parser.add_argument(
"-opencl", type=int, action="append", nargs="*", help=help_msg["opencl"]
)
parser.add_argument(
"--geometry-only",
action="store_true",
default=args_defaults["geometry_only"],
help=help_msg["geometry_only"],
)
parser.add_argument(
"--geometry-fixed",
@@ -188,8 +201,18 @@ def cli():
default=args_defaults["write_processed"],
help=help_msg["write_processed"],
)
parser.add_argument("--log-level", type=int, default=args_defaults["log_level"], help=help_msg["log_level"])
parser.add_argument("--log-file", action="store_true", default=args_defaults["log_file"], help=help_msg["log_file"])
parser.add_argument(
"--log-level",
type=int,
default=args_defaults["log_level"],
help=help_msg["log_level"],
)
parser.add_argument(
"--log-file",
action="store_true",
default=args_defaults["log_file"],
help=help_msg["log_file"],
)
args = parser.parse_args()
results = run_main(args)
@@ -209,9 +232,7 @@ def run_main(args):
"""
results = {}
logging_config(level=args.log_level, log_file=args.log_file)
config.sim_config = config.SimulationConfig(args)
# MPI running with (OpenMP/CUDA/OpenCL)
@@ -222,4 +243,5 @@ def run_main(args):
context = Context()
results = context.run()
return results

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -18,6 +18,7 @@
import decimal as d
from collections import OrderedDict
from importlib import import_module
import numpy as np
@@ -56,10 +57,11 @@ class FDTDGrid:
self.pmls["formulation"] = "HORIPML"
self.pmls["cfs"] = []
self.pmls["slabs"] = []
# Ordered dictionary required so that PMLs are always updated in the
# same order. The order itself does not matter, however, if must be the
# same from model to model otherwise the numerical precision from adding
# the PML corrections will be different.
# Ordered dictionary required so *updating* the PMLs always follows the
# same order (the order for *building* PMLs does not matter). The order
# itself does not matter, however, if must be the same from model to
# model otherwise the numerical precision from adding the PML
# corrections will be different.
self.pmls["thickness"] = OrderedDict((key, 10) for key in PML.boundaryIDs)
self.materials = []
@@ -127,30 +129,67 @@ class FDTDGrid:
def initialise_field_arrays(self):
"""Initialise arrays for the electric and magnetic field components."""
self.Ex = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"])
self.Ey = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"])
self.Ez = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"])
self.Hx = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"])
self.Hy = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"])
self.Hz = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"])
self.Ex = np.zeros(
(self.nx + 1, self.ny + 1, self.nz + 1),
dtype=config.sim_config.dtypes["float_or_double"],
)
self.Ey = np.zeros(
(self.nx + 1, self.ny + 1, self.nz + 1),
dtype=config.sim_config.dtypes["float_or_double"],
)
self.Ez = np.zeros(
(self.nx + 1, self.ny + 1, self.nz + 1),
dtype=config.sim_config.dtypes["float_or_double"],
)
self.Hx = np.zeros(
(self.nx + 1, self.ny + 1, self.nz + 1),
dtype=config.sim_config.dtypes["float_or_double"],
)
self.Hy = np.zeros(
(self.nx + 1, self.ny + 1, self.nz + 1),
dtype=config.sim_config.dtypes["float_or_double"],
)
self.Hz = np.zeros(
(self.nx + 1, self.ny + 1, self.nz + 1),
dtype=config.sim_config.dtypes["float_or_double"],
)
def initialise_std_update_coeff_arrays(self):
"""Initialise arrays for storing update coefficients."""
self.updatecoeffsE = np.zeros((len(self.materials), 5), dtype=config.sim_config.dtypes["float_or_double"])
self.updatecoeffsH = np.zeros((len(self.materials), 5), dtype=config.sim_config.dtypes["float_or_double"])
self.updatecoeffsE = np.zeros(
(len(self.materials), 5), dtype=config.sim_config.dtypes["float_or_double"]
)
self.updatecoeffsH = np.zeros(
(len(self.materials), 5), dtype=config.sim_config.dtypes["float_or_double"]
)
def initialise_dispersive_arrays(self):
"""Initialise field arrays when there are dispersive materials present."""
self.Tx = np.zeros(
(config.get_model_config().materials["maxpoles"], self.nx + 1, self.ny + 1, self.nz + 1),
(
config.get_model_config().materials["maxpoles"],
self.nx + 1,
self.ny + 1,
self.nz + 1,
),
dtype=config.get_model_config().materials["dispersivedtype"],
)
self.Ty = np.zeros(
(config.get_model_config().materials["maxpoles"], self.nx + 1, self.ny + 1, self.nz + 1),
(
config.get_model_config().materials["maxpoles"],
self.nx + 1,
self.ny + 1,
self.nz + 1,
),
dtype=config.get_model_config().materials["dispersivedtype"],
)
self.Tz = np.zeros(
(config.get_model_config().materials["maxpoles"], self.nx + 1, self.ny + 1, self.nz + 1),
(
config.get_model_config().materials["maxpoles"],
self.nx + 1,
self.ny + 1,
self.nz + 1,
),
dtype=config.get_model_config().materials["dispersivedtype"],
)
@@ -184,7 +223,9 @@ class FDTDGrid:
solidarray = self.nx * self.ny * self.nz * np.dtype(np.uint32).itemsize
# 12 x rigidE array components + 6 x rigidH array components
rigidarrays = (12 + 6) * self.nx * self.ny * self.nz * np.dtype(np.int8).itemsize
rigidarrays = (
(12 + 6) * self.nx * self.ny * self.nz * np.dtype(np.int8).itemsize
)
# 6 x field arrays + 6 x ID arrays
fieldarrays = (
@@ -287,14 +328,24 @@ class FDTDGrid:
def calculate_dt(self):
"""Calculate time step at the CFL limit."""
if config.get_model_config().mode == "2D TMx":
self.dt = 1 / (config.sim_config.em_consts["c"] * np.sqrt((1 / self.dy**2) + (1 / self.dz**2)))
self.dt = 1 / (
config.sim_config.em_consts["c"]
* np.sqrt((1 / self.dy**2) + (1 / self.dz**2))
)
elif config.get_model_config().mode == "2D TMy":
self.dt = 1 / (config.sim_config.em_consts["c"] * np.sqrt((1 / self.dx**2) + (1 / self.dz**2)))
self.dt = 1 / (
config.sim_config.em_consts["c"]
* np.sqrt((1 / self.dx**2) + (1 / self.dz**2))
)
elif config.get_model_config().mode == "2D TMz":
self.dt = 1 / (config.sim_config.em_consts["c"] * np.sqrt((1 / self.dx**2) + (1 / self.dy**2)))
self.dt = 1 / (
config.sim_config.em_consts["c"]
* np.sqrt((1 / self.dx**2) + (1 / self.dy**2))
)
else:
self.dt = 1 / (
config.sim_config.em_consts["c"] * np.sqrt((1 / self.dx**2) + (1 / self.dy**2) + (1 / self.dz**2))
config.sim_config.em_consts["c"]
* np.sqrt((1 / self.dx**2) + (1 / self.dy**2) + (1 / self.dz**2))
)
# Round down time step to nearest float with precision one less than
@@ -309,6 +360,8 @@ class CUDAGrid(FDTDGrid):
def __init__(self):
super().__init__()
self.gpuarray = import_module("pycuda.gpuarray")
# Threads per block - used for main electric/magnetic field updates
self.tpb = (128, 1, 1)
# Blocks per grid - used for main electric/magnetic field updates
@@ -319,82 +372,83 @@ class CUDAGrid(FDTDGrid):
magnetic field arrays on a GPU.
"""
self.bpg = (int(np.ceil(((self.nx + 1) * (self.ny + 1) * (self.nz + 1)) / self.tpb[0])), 1, 1)
self.bpg = (
int(np.ceil(((self.nx + 1) * (self.ny + 1) * (self.nz + 1)) / self.tpb[0])),
1,
1,
)
def htod_geometry_arrays(self, queue=None):
def htod_geometry_arrays(self):
"""Initialise an array for cell edge IDs (ID) on compute device."""
self.ID_dev = self.gpuarray.to_gpu(self.ID)
def htod_field_arrays(self):
"""Initialise field arrays on compute device."""
self.Ex_dev = self.gpuarray.to_gpu(self.Ex)
self.Ey_dev = self.gpuarray.to_gpu(self.Ey)
self.Ez_dev = self.gpuarray.to_gpu(self.Ez)
self.Hx_dev = self.gpuarray.to_gpu(self.Hx)
self.Hy_dev = self.gpuarray.to_gpu(self.Hy)
self.Hz_dev = self.gpuarray.to_gpu(self.Hz)
def htod_dispersive_arrays(self):
"""Initialise dispersive material coefficient arrays on compute device."""
self.updatecoeffsdispersive_dev = self.gpuarray.to_gpu(
self.updatecoeffsdispersive
)
self.Tx_dev = self.gpuarray.to_gpu(self.Tx)
self.Ty_dev = self.gpuarray.to_gpu(self.Ty)
self.Tz_dev = self.gpuarray.to_gpu(self.Tz)
class OpenCLGrid(FDTDGrid):
"""Additional grid methods for solving on compute device using OpenCL."""
def __init__(self):
super().__init__()
self.clarray = import_module("pyopencl.array")
def htod_geometry_arrays(self, queue):
"""Initialise an array for cell edge IDs (ID) on compute device.
Args:
queue: pyopencl queue.
"""
if config.sim_config.general["solver"] == "cuda":
import pycuda.gpuarray as gpuarray
self.ID_dev = self.clarray.to_device(queue, self.ID)
self.ID_dev = gpuarray.to_gpu(self.ID)
elif config.sim_config.general["solver"] == "opencl":
import pyopencl.array as clarray
self.ID_dev = clarray.to_device(queue, self.ID)
def htod_field_arrays(self, queue=None):
def htod_field_arrays(self, queue):
"""Initialise field arrays on compute device.
Args:
queue: pyopencl queue.
"""
if config.sim_config.general["solver"] == "cuda":
import pycuda.gpuarray as gpuarray
self.Ex_dev = self.clarray.to_device(queue, self.Ex)
self.Ey_dev = self.clarray.to_device(queue, self.Ey)
self.Ez_dev = self.clarray.to_device(queue, self.Ez)
self.Hx_dev = self.clarray.to_device(queue, self.Hx)
self.Hy_dev = self.clarray.to_device(queue, self.Hy)
self.Hz_dev = self.clarray.to_device(queue, self.Hz)
self.Ex_dev = gpuarray.to_gpu(self.Ex)
self.Ey_dev = gpuarray.to_gpu(self.Ey)
self.Ez_dev = gpuarray.to_gpu(self.Ez)
self.Hx_dev = gpuarray.to_gpu(self.Hx)
self.Hy_dev = gpuarray.to_gpu(self.Hy)
self.Hz_dev = gpuarray.to_gpu(self.Hz)
elif config.sim_config.general["solver"] == "opencl":
import pyopencl.array as clarray
self.Ex_dev = clarray.to_device(queue, self.Ex)
self.Ey_dev = clarray.to_device(queue, self.Ey)
self.Ez_dev = clarray.to_device(queue, self.Ez)
self.Hx_dev = clarray.to_device(queue, self.Hx)
self.Hy_dev = clarray.to_device(queue, self.Hy)
self.Hz_dev = clarray.to_device(queue, self.Hz)
def htod_dispersive_arrays(self, queue=None):
def htod_dispersive_arrays(self, queue):
"""Initialise dispersive material coefficient arrays on compute device.
Args:
queue: pyopencl queue.
"""
if config.sim_config.general["solver"] == "cuda":
import pycuda.gpuarray as gpuarray
self.Tx_dev = gpuarray.to_gpu(self.Tx)
self.Ty_dev = gpuarray.to_gpu(self.Ty)
self.Tz_dev = gpuarray.to_gpu(self.Tz)
self.updatecoeffsdispersive_dev = gpuarray.to_gpu(self.updatecoeffsdispersive)
elif config.sim_config.general["solver"] == "opencl":
import pyopencl.array as clarray
self.Tx_dev = clarray.to_device(queue, self.Tx)
self.Ty_dev = clarray.to_device(queue, self.Ty)
self.Tz_dev = clarray.to_device(queue, self.Tz)
self.updatecoeffsdispersive_dev = clarray.to_device(queue, self.updatecoeffsdispersive)
class OpenCLGrid(CUDAGrid):
"""Additional grid methods for solving on compute device using OpenCL."""
def __init__(self):
super().__init__()
def set_blocks_per_grid(self):
pass
self.updatecoeffsdispersive_dev = self.clarray.to_device(
queue, self.updatecoeffsdispersive
)
# self.updatecoeffsdispersive_dev = self.clarray.to_device(queue, np.ones((95,95,95), dtype=np.float32))
self.Tx_dev = self.clarray.to_device(queue, self.Tx)
self.Ty_dev = self.clarray.to_device(queue, self.Ty)
self.Tz_dev = self.clarray.to_device(queue, self.Tz)
def dispersion_analysis(G):
@@ -428,22 +482,16 @@ def dispersion_analysis(G):
results["error"] = "user waveform detected."
else:
# User-defined waveform
if waveform.type == "user":
iterations = G.iterations
# Built-in waveform
else:
# Time to analyse waveform - 4*pulse_width as using entire
# time window can result in demanding FFT
waveform.calculate_coefficients()
iterations = round_value(4 * waveform.chi / G.dt)
if iterations > G.iterations:
iterations = G.iterations
# Time to analyse waveform - 4*pulse_width as using entire
# time window can result in demanding FFT
waveform.calculate_coefficients()
iterations = round_value(4 * waveform.chi / G.dt)
iterations = min(iterations, G.iterations)
waveformvalues = np.zeros(G.iterations)
for iteration in range(G.iterations):
waveformvalues[iteration] = waveform.calculate_value(iteration * G.dt, G.dt)
waveformvalues[iteration] = waveform.calculate_value(
iteration * G.dt, G.dt
)
# Ensure source waveform is not being overly truncated before attempting any FFT
if np.abs(waveformvalues[-1]) < np.abs(np.amax(waveformvalues)) / 100:
@@ -456,7 +504,10 @@ def dispersion_analysis(G):
try:
freqthres = (
np.where(
power[freqmaxpower:] < -config.get_model_config().numdispersion["highestfreqthres"]
power[freqmaxpower:]
< -config.get_model_config().numdispersion[
"highestfreqthres"
]
)[0][0]
+ freqmaxpower
)
@@ -475,7 +526,8 @@ def dispersion_analysis(G):
# If waveform is truncated don't do any further analysis
else:
results["error"] = (
"waveform does not fit within specified " + "time window and is therefore being truncated."
"waveform does not fit within specified "
+ "time window and is therefore being truncated."
)
else:
results["error"] = "no waveform detected."
@@ -523,9 +575,14 @@ def dispersion_analysis(G):
results["N"] = minwavelength / delta
# Check grid sampling will result in physical wave propagation
if int(np.floor(results["N"])) >= config.get_model_config().numdispersion["mingridsampling"]:
if (
int(np.floor(results["N"]))
>= config.get_model_config().numdispersion["mingridsampling"]
):
# Numerical phase velocity
vp = np.pi / (results["N"] * np.arcsin((1 / S) * np.sin((np.pi * S) / results["N"])))
vp = np.pi / (
results["N"] * np.arcsin((1 / S) * np.sin((np.pi * S) / results["N"]))
)
# Physical phase velocity error (percentage)
results["deltavp"] = (((vp * config.c) - config.c) / config.c) * 100

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -48,7 +48,11 @@ def process_python_include_code(inputfile, usernamespace):
"""
# Strip out any newline characters and comments that must begin with double hashes
inputlines = [line.rstrip() for line in inputfile if (not line.startswith("##") and line.rstrip("\n"))]
inputlines = [
line.rstrip()
for line in inputfile
if (not line.startswith("##") and line.rstrip("\n"))
]
# Rewind input file in preparation for any subsequent reading function
inputfile.seek(0)
@@ -74,7 +78,8 @@ def process_python_include_code(inputfile, usernamespace):
x += 1
if x == len(inputlines):
logger.exception(
"Cannot find the end of the Python code " + "block, i.e. missing #end_python: command."
"Cannot find the end of the Python code "
+ "block, i.e. missing #end_python: command."
)
raise SyntaxError
# Compile code for faster execution
@@ -147,7 +152,9 @@ def process_include_files(hashcmds):
# See if file exists at specified path and if not try input file directory
includefile = Path(includefile)
if not includefile.exists():
includefile = Path(config.sim_config.input_file_path.parent, includefile)
includefile = Path(
config.sim_config.input_file_path.parent, includefile
)
with open(includefile, "r") as f:
# Strip out any newline characters and comments that must begin with double hashes
@@ -185,8 +192,7 @@ def write_processed_file(processedlines):
f.write(f"{item}")
logger.info(
f"Written input commands, after processing any Python "
+ f"code and include commands, to file: {processedfile}\n"
f"Written input commands, after processing any Python code and include commands, to file: {processedfile}\n"
)
@@ -218,8 +224,8 @@ def check_cmd_names(processedlines, checkessential=True):
"#title",
"#omp_threads",
"#time_step_stability_factor",
"#pml_formulation",
"#pml_cells",
"#excitation_file",
"#src_steps",
"#rx_steps",
"#output_dir",
@@ -247,9 +253,11 @@ def check_cmd_names(processedlines, checkessential=True):
"#magnetic_dipole",
"#transmission_line",
"#discrete_plane_wave",
"#excitation_file",
"#rx",
"#rx_array",
"#snapshot",
"#pml_cfs",
"#include_file",
]
}
@@ -289,7 +297,9 @@ def check_cmd_names(processedlines, checkessential=True):
# are no parameters for a command, e.g. for #taguchi:
if " " not in cmdparams[0] and len(cmdparams.strip("\n")) != 0:
logger.exception(
"There must be a space between the command name " + "and parameters in " + processedlines[lindex]
"There must be a space between the command name "
+ "and parameters in "
+ processedlines[lindex]
)
raise SyntaxError
@@ -312,7 +322,11 @@ def check_cmd_names(processedlines, checkessential=True):
if singlecmds[cmdname] is None:
singlecmds[cmdname] = cmd[1].strip(" \t\n")
else:
logger.exception("You can only have a single instance of " + cmdname + " in your model")
logger.exception(
"You can only have a single instance of "
+ cmdname
+ " in your model"
)
raise SyntaxError
elif cmdname in multiplecmds:
@@ -384,7 +398,10 @@ def parse_hash_commands(scene):
for key, value in sorted(usernamespace.items()):
if key != "__builtins__":
uservars += f"{key}: {value}, "
logger.info(f"Constants/variables used/available for Python scripting: " + f"{{{uservars[:-2]}}}\n")
logger.info(
f"Constants/variables used/available for Python scripting: "
+ f"{{{uservars[:-2]}}}\n"
)
# Write a file containing the input commands after Python or include
# file commands have been processed
@@ -396,32 +413,3 @@ def parse_hash_commands(scene):
scene.add(user_obj)
return scene
class Capturing(list):
"""Context manager to capture standard output stream."""
# https://stackoverflow.com/questions/16571150/how-to-capture-stdout-output-from-a-python-function-call
def __enter__(self):
self._stdout = sys.stdout
sys.stdout = self._stringio = StringIO()
return self
def __exit__(self, *args):
self.extend(self._stringio.getvalue().splitlines())
del self._stringio # free up some memory
sys.stdout = self._stdout
def user_libs_fn_to_scene_obj(f, *args, **kwargs):
"""Function to convert library functions in the toolboxes directory
into geometry objects which can be added to the scene.
"""
with Capturing() as str_cmds:
f(*args, **kwargs)
user_objects = get_user_objects(str_cmds, checkessential=False)
return user_objects

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -38,27 +38,6 @@ from .utilities.utilities import round_value
logger = logging.getLogger(__name__)
def check_averaging(averaging):
"""Check and set material averaging value.
Args:
averaging: string for input value from hash command - should be 'y'
or 'n'.
Returns:
averaging: boolean for geometry object material averaging.
"""
if averaging == "y":
averaging = True
elif averaging == "n":
averaging = False
else:
logger.exception("Averaging should be either y or n")
return averaging
def process_geometrycmds(geometry):
"""Checks the validity of command parameters, creates instances of classes
of parameters, and calls functions to directly set arrays solid, rigid
@@ -80,7 +59,9 @@ def process_geometrycmds(geometry):
from .cmds_geometry.geometry_objects_read import GeometryObjectsRead
if len(tmp) != 6:
logger.exception("'" + " ".join(tmp) + "'" + " requires exactly five parameters")
logger.exception(
"'" + " ".join(tmp) + "'" + " requires exactly five parameters"
)
raise ValueError
p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
@@ -90,7 +71,9 @@ def process_geometrycmds(geometry):
elif tmp[0] == "#edge:":
if len(tmp) != 8:
logger.exception("'" + " ".join(tmp) + "'" + " requires exactly seven parameters")
logger.exception(
"'" + " ".join(tmp) + "'" + " requires exactly seven parameters"
)
raise ValueError
edge = Edge(
@@ -103,7 +86,9 @@ def process_geometrycmds(geometry):
elif tmp[0] == "#plate:":
if len(tmp) < 8:
logger.exception("'" + " ".join(tmp) + "'" + " requires at least seven parameters")
logger.exception(
"'" + " ".join(tmp) + "'" + " requires at least seven parameters"
)
raise ValueError
# Isotropic case
@@ -123,14 +108,18 @@ def process_geometrycmds(geometry):
)
else:
logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given")
logger.exception(
"'" + " ".join(tmp) + "'" + " too many parameters have been given"
)
raise ValueError
scene_objects.append(plate)
elif tmp[0] == "#triangle:":
if len(tmp) < 12:
logger.exception("'" + " ".join(tmp) + "'" + " requires at least eleven parameters")
logger.exception(
"'" + " ".join(tmp) + "'" + " requires at least eleven parameters"
)
raise ValueError
p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
@@ -140,26 +129,40 @@ def process_geometrycmds(geometry):
# Isotropic case with no user specified averaging
if len(tmp) == 12:
triangle = Triangle(p1=p1, p2=p2, p3=p3, thickness=thickness, material_id=tmp[11])
triangle = Triangle(
p1=p1, p2=p2, p3=p3, thickness=thickness, material_id=tmp[11]
)
# Isotropic case with user specified averaging
elif len(tmp) == 13:
averaging = check_averaging(tmp[12].lower())
triangle = Triangle(p1=p1, p2=p2, p3=p3, thickness=thickness, material_id=tmp[11], averaging=averaging)
triangle = Triangle(
p1=p1,
p2=p2,
p3=p3,
thickness=thickness,
material_id=tmp[11],
averaging=tmp[12].lower(),
)
# Uniaxial anisotropic case
elif len(tmp) == 14:
triangle = Triangle(p1=p1, p2=p2, p3=p3, thickness=thickness, material_ids=tmp[11:])
triangle = Triangle(
p1=p1, p2=p2, p3=p3, thickness=thickness, material_ids=tmp[11:]
)
else:
logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given")
logger.exception(
"'" + " ".join(tmp) + "'" + " too many parameters have been given"
)
raise ValueError
scene_objects.append(triangle)
elif tmp[0] == "#box:":
if len(tmp) < 8:
logger.exception("'" + " ".join(tmp) + "'" + " requires at least seven parameters")
logger.exception(
"'" + " ".join(tmp) + "'" + " requires at least seven parameters"
)
raise ValueError
p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
@@ -171,22 +174,25 @@ def process_geometrycmds(geometry):
# Isotropic case with user specified averaging
elif len(tmp) == 9:
averaging = check_averaging(tmp[8].lower())
box = Box(p1=p1, p2=p2, material_id=tmp[7], averaging=averaging)
box = Box(p1=p1, p2=p2, material_id=tmp[7], averaging=tmp[8].lower())
# Uniaxial anisotropic case
elif len(tmp) == 10:
box = Box(p1=p1, p2=p2, material_ids=tmp[7:])
else:
logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given")
logger.exception(
"'" + " ".join(tmp) + "'" + " too many parameters have been given"
)
raise ValueError
scene_objects.append(box)
elif tmp[0] == "#cylinder:":
if len(tmp) < 9:
logger.exception("'" + " ".join(tmp) + "'" + " requires at least eight parameters")
logger.exception(
"'" + " ".join(tmp) + "'" + " requires at least eight parameters"
)
raise ValueError
p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
@@ -199,22 +205,27 @@ def process_geometrycmds(geometry):
# Isotropic case with user specified averaging
elif len(tmp) == 10:
averaging = check_averaging(tmp[9].lower())
cylinder = Cylinder(p1=p1, p2=p2, r=r, material_id=tmp[8], averaging=averaging)
cylinder = Cylinder(
p1=p1, p2=p2, r=r, material_id=tmp[8], averaging=tmp[9].lower()
)
# Uniaxial anisotropic case
elif len(tmp) == 11:
cylinder = Cylinder(p1=p1, p2=p2, r=r, material_ids=tmp[8:])
else:
logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given")
logger.exception(
"'" + " ".join(tmp) + "'" + " too many parameters have been given"
)
raise ValueError
scene_objects.append(cylinder)
elif tmp[0] == "#cone:":
if len(tmp) < 10:
logger.exception("'" + " ".join(tmp) + "'" + " requires at least nine parameters")
logger.exception(
"'" + " ".join(tmp) + "'" + " requires at least nine parameters"
)
raise ValueError
p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
@@ -228,22 +239,32 @@ def process_geometrycmds(geometry):
# Isotropic case with user specified averaging
elif len(tmp) == 11:
averaging = check_averaging(tmp[10].lower())
cone = Cone(p1=p1, p2=p2, r1=r1, r2=r2, material_id=tmp[9], averaging=averaging)
cone = Cone(
p1=p1,
p2=p2,
r1=r1,
r2=r2,
material_id=tmp[9],
averaging=tmp[10].lower(),
)
# Uniaxial anisotropic case
elif len(tmp) == 12:
cone = Cone(p1=p1, p2=p2, r1=r1, r2=r2, material_ids=tmp[9:])
else:
logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given")
logger.exception(
"'" + " ".join(tmp) + "'" + " too many parameters have been given"
)
raise ValueError
scene_objects.append(cone)
elif tmp[0] == "#cylindrical_sector:":
if len(tmp) < 10:
logger.exception("'" + " ".join(tmp) + "'" + " requires at least nine parameters")
logger.exception(
"'" + " ".join(tmp) + "'" + " requires at least nine parameters"
)
raise ValueError
normal = tmp[1].lower()
@@ -271,7 +292,6 @@ def process_geometrycmds(geometry):
# Isotropic case with user specified averaging
elif len(tmp) == 11:
averaging = check_averaging(tmp[10].lower())
cylindrical_sector = CylindricalSector(
normal=normal,
ctr1=ctr1,
@@ -281,7 +301,7 @@ def process_geometrycmds(geometry):
r=r,
start=start,
end=end,
averaging=averaging,
averaging=tmp[10].lower(),
material_id=tmp[9],
)
@@ -300,14 +320,18 @@ def process_geometrycmds(geometry):
)
else:
logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given")
logger.exception(
"'" + " ".join(tmp) + "'" + " too many parameters have been given"
)
raise ValueError
scene_objects.append(cylindrical_sector)
elif tmp[0] == "#sphere:":
if len(tmp) < 6:
logger.exception("'" + " ".join(tmp) + "'" + " requires at least five parameters")
logger.exception(
"'" + " ".join(tmp) + "'" + " requires at least five parameters"
)
raise ValueError
p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
@@ -319,22 +343,27 @@ def process_geometrycmds(geometry):
# Isotropic case with user specified averaging
elif len(tmp) == 7:
averaging = check_averaging(tmp[6].lower())
sphere = Sphere(p1=p1, r=r, material_id=tmp[5], averaging=averaging)
sphere = Sphere(
p1=p1, r=r, material_id=tmp[5], averaging=tmp[6].lower()
)
# Uniaxial anisotropic case
elif len(tmp) == 8:
sphere = Sphere(p1=p1, r=r, material_id=tmp[5:])
else:
logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given")
logger.exception(
"'" + " ".join(tmp) + "'" + " too many parameters have been given"
)
raise ValueError
scene_objects.append(sphere)
elif tmp[0] == "#ellipsoid:":
if len(tmp) < 8:
logger.exception("'" + " ".join(tmp) + "'" + " requires at least seven parameters")
logger.exception(
"'" + " ".join(tmp) + "'" + " requires at least seven parameters"
)
raise ValueError
p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
@@ -348,15 +377,23 @@ def process_geometrycmds(geometry):
# Isotropic case with user specified averaging
elif len(tmp) == 9:
averaging = check_averaging(tmp[8].lower())
ellipsoid = Ellipsoid(p1=p1, xr=xr, yr=yr, zr=zr, material_id=tmp[7], averaging=averaging)
ellipsoid = Ellipsoid(
p1=p1,
xr=xr,
yr=yr,
zr=zr,
material_id=tmp[7],
averaging=tmp[8].lower(),
)
# Uniaxial anisotropic case
elif len(tmp) == 8:
ellipsoid = Ellipsoid(p1=p1, xr=xr, yr=yr, zr=zr, material_id=tmp[7:])
else:
logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given")
logger.exception(
"'" + " ".join(tmp) + "'" + " too many parameters have been given"
)
raise ValueError
scene_objects.append(ellipsoid)
@@ -365,7 +402,9 @@ def process_geometrycmds(geometry):
# Default is no dielectric smoothing for a fractal box
if len(tmp) < 14:
logger.exception("'" + " ".join(tmp) + "'" + " requires at least thirteen parameters")
logger.exception(
"'" + " ".join(tmp) + "'" + " requires at least thirteen parameters"
)
raise ValueError
p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
@@ -410,7 +449,9 @@ def process_geometrycmds(geometry):
averaging=tmp[15].lower(),
)
else:
logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given")
logger.exception(
"'" + " ".join(tmp) + "'" + " too many parameters have been given"
)
raise ValueError
scene_objects.append(fb)
@@ -421,7 +462,12 @@ def process_geometrycmds(geometry):
if tmp[0] == "#add_surface_roughness:":
if len(tmp) < 13:
logger.exception("'" + " ".join(tmp) + "'" + " requires at least twelve parameters")
logger.exception(
"'"
+ " ".join(tmp)
+ "'"
+ " requires at least twelve parameters"
)
raise ValueError
p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
@@ -451,14 +497,24 @@ def process_geometrycmds(geometry):
seed=int(tmp[13]),
)
else:
logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given")
logger.exception(
"'"
+ " ".join(tmp)
+ "'"
+ " too many parameters have been given"
)
raise ValueError
scene_objects.append(asr)
if tmp[0] == "#add_surface_water:":
if len(tmp) != 9:
logger.exception("'" + " ".join(tmp) + "'" + " requires exactly eight parameters")
logger.exception(
"'"
+ " ".join(tmp)
+ "'"
+ " requires exactly eight parameters"
)
raise ValueError
p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
@@ -466,12 +522,19 @@ def process_geometrycmds(geometry):
depth = float(tmp[7])
fractal_box_id = tmp[8]
asf = AddSurfaceWater(p1=p1, p2=p2, depth=depth, fractal_box_id=fractal_box_id)
asf = AddSurfaceWater(
p1=p1, p2=p2, depth=depth, fractal_box_id=fractal_box_id
)
scene_objects.append(asf)
if tmp[0] == "#add_grass:":
if len(tmp) < 12:
logger.exception("'" + " ".join(tmp) + "'" + " requires at least eleven parameters")
logger.exception(
"'"
+ " ".join(tmp)
+ "'"
+ " requires at least eleven parameters"
)
raise ValueError
p1 = (float(tmp[1]), float(tmp[2]), float(tmp[3]))
@@ -501,7 +564,12 @@ def process_geometrycmds(geometry):
seed=int(tmp[12]),
)
else:
logger.exception("'" + " ".join(tmp) + "'" + " too many parameters have been given")
logger.exception(
"'"
+ " ".join(tmp)
+ "'"
+ " too many parameters have been given"
)
raise ValueError
scene_objects.append(grass)

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -19,9 +19,11 @@
import logging
from .cmds_multiuse import (
PMLCFS,
AddDebyeDispersion,
AddDrudeDispersion,
AddLorentzDispersion,
ExcitationFile,
GeometryObjectsWrite,
GeometryView,
HertzianDipole,
@@ -60,10 +62,19 @@ def process_multicmds(multicmds):
for cmdinstance in multicmds[cmdname]:
tmp = cmdinstance.split()
if len(tmp) != 4:
logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires exactly four parameters")
logger.exception(
"'"
+ cmdname
+ ": "
+ " ".join(tmp)
+ "'"
+ " requires exactly four parameters"
)
raise ValueError
waveform = Waveform(wave_type=tmp[0], amp=float(tmp[1]), freq=float(tmp[2]), id=tmp[3])
waveform = Waveform(
wave_type=tmp[0], amp=float(tmp[1]), freq=float(tmp[2]), id=tmp[3]
)
scene_objects.append(waveform)
cmdname = "#voltage_source"
@@ -87,7 +98,14 @@ def process_multicmds(multicmds):
end=float(tmp[7]),
)
else:
logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires at least six parameters")
logger.exception(
"'"
+ cmdname
+ ": "
+ " ".join(tmp)
+ "'"
+ " requires at least six parameters"
)
raise ValueError
scene_objects.append(voltage_source)
@@ -97,11 +115,20 @@ def process_multicmds(multicmds):
for cmdinstance in multicmds[cmdname]:
tmp = cmdinstance.split()
if len(tmp) < 5:
logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires at least five parameters")
logger.exception(
"'"
+ cmdname
+ ": "
+ " ".join(tmp)
+ "'"
+ " requires at least five parameters"
)
raise ValueError
if len(tmp) == 5:
hertzian_dipole = HertzianDipole(
polarisation=tmp[0], p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])), waveform_id=tmp[4]
polarisation=tmp[0],
p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])),
waveform_id=tmp[4],
)
elif len(tmp) == 7:
hertzian_dipole = HertzianDipole(
@@ -112,7 +139,9 @@ def process_multicmds(multicmds):
end=float(tmp[6]),
)
else:
logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " too many parameters")
logger.exception(
"'" + cmdname + ": " + " ".join(tmp) + "'" + " too many parameters"
)
raise ValueError
scene_objects.append(hertzian_dipole)
@@ -122,11 +151,20 @@ def process_multicmds(multicmds):
for cmdinstance in multicmds[cmdname]:
tmp = cmdinstance.split()
if len(tmp) < 5:
logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires at least five parameters")
logger.exception(
"'"
+ cmdname
+ ": "
+ " ".join(tmp)
+ "'"
+ " requires at least five parameters"
)
raise ValueError
if len(tmp) == 5:
magnetic_dipole = MagneticDipole(
polarisation=tmp[0], p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])), waveform_id=tmp[4]
polarisation=tmp[0],
p1=(float(tmp[1]), float(tmp[2]), float(tmp[3])),
waveform_id=tmp[4],
)
elif len(tmp) == 7:
magnetic_dipole = MagneticDipole(
@@ -137,7 +175,9 @@ def process_multicmds(multicmds):
end=float(tmp[6]),
)
else:
logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " too many parameters")
logger.exception(
"'" + cmdname + ": " + " ".join(tmp) + "'" + " too many parameters"
)
raise ValueError
scene_objects.append(magnetic_dipole)
@@ -147,7 +187,14 @@ def process_multicmds(multicmds):
for cmdinstance in multicmds[cmdname]:
tmp = cmdinstance.split()
if len(tmp) < 6:
logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires at least six parameters")
logger.exception(
"'"
+ cmdname
+ ": "
+ " ".join(tmp)
+ "'"
+ " requires at least six parameters"
)
raise ValueError
if len(tmp) == 6:
@@ -167,7 +214,9 @@ def process_multicmds(multicmds):
end=tmp[7],
)
else:
logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " too many parameters")
logger.exception(
"'" + cmdname + ": " + " ".join(tmp) + "'" + " too many parameters"
)
raise ValueError
scene_objects.append(tl)
@@ -254,18 +303,46 @@ def process_multicmds(multicmds):
raise ValueError
scene_objects.append(plWave)
cmd = "#excitation_file"
if multicmds[cmd] is not None:
for cmdinstance in multicmds[cmdname]:
tmp = cmdinstance.split()
if len(tmp) not in [1, 3]:
logger.exception(f"{cmd} requires either one or three parameter(s)")
raise ValueError
if len(tmp) > 1:
ex_file = ExcitationFile(
filepath=tmp[0], kind=tmp[1], fill_value=tmp[2]
)
else:
ex_file = ExcitationFile(filepath=tmp[0])
scene_objects.append(ex_file)
cmdname = "#rx"
if multicmds[cmdname] is not None:
for cmdinstance in multicmds[cmdname]:
tmp = cmdinstance.split()
if len(tmp) != 3 and len(tmp) < 5:
logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " has an incorrect number of parameters")
logger.exception(
"'"
+ cmdname
+ ": "
+ " ".join(tmp)
+ "'"
+ " has an incorrect number of parameters"
)
raise ValueError
if len(tmp) == 3:
rx = Rx(p1=(float(tmp[0]), float(tmp[1]), float(tmp[2])))
else:
rx = Rx(p1=(float(tmp[0]), float(tmp[1]), float(tmp[2])), id=tmp[3], outputs=" ".join(tmp[4:]))
rx = Rx(
p1=(float(tmp[0]), float(tmp[1]), float(tmp[2])),
id=tmp[3],
outputs=[" ".join(tmp[4:])],
)
scene_objects.append(rx)
@@ -274,7 +351,14 @@ def process_multicmds(multicmds):
for cmdinstance in multicmds[cmdname]:
tmp = cmdinstance.split()
if len(tmp) != 9:
logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires exactly nine parameters")
logger.exception(
"'"
+ cmdname
+ ": "
+ " ".join(tmp)
+ "'"
+ " requires exactly nine parameters"
)
raise ValueError
p1 = (float(tmp[0]), float(tmp[1]), float(tmp[2]))
@@ -289,7 +373,14 @@ def process_multicmds(multicmds):
for cmdinstance in multicmds[cmdname]:
tmp = cmdinstance.split()
if len(tmp) != 11:
logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires exactly eleven parameters")
logger.exception(
"'"
+ cmdname
+ ": "
+ " ".join(tmp)
+ "'"
+ " requires exactly eleven parameters"
)
raise ValueError
p1 = (float(tmp[0]), float(tmp[1]), float(tmp[2]))
@@ -299,7 +390,9 @@ def process_multicmds(multicmds):
try:
iterations = int(tmp[9])
snapshot = Snapshot(p1=p1, p2=p2, dl=dl, iterations=iterations, filename=filename)
snapshot = Snapshot(
p1=p1, p2=p2, dl=dl, iterations=iterations, filename=filename
)
except ValueError:
time = float(tmp[9])
@@ -312,10 +405,23 @@ def process_multicmds(multicmds):
for cmdinstance in multicmds[cmdname]:
tmp = cmdinstance.split()
if len(tmp) != 5:
logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires exactly five parameters")
logger.exception(
"'"
+ cmdname
+ ": "
+ " ".join(tmp)
+ "'"
+ " requires exactly five parameters"
)
raise ValueError
material = Material(er=float(tmp[0]), se=float(tmp[1]), mr=float(tmp[2]), sm=float(tmp[3]), id=tmp[4])
material = Material(
er=float(tmp[0]),
se=float(tmp[1]),
mr=float(tmp[2]),
sm=float(tmp[3]),
id=tmp[4],
)
scene_objects.append(material)
cmdname = "#add_dispersion_debye"
@@ -324,7 +430,14 @@ def process_multicmds(multicmds):
tmp = cmdinstance.split()
if len(tmp) < 4:
logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires at least four parameters")
logger.exception(
"'"
+ cmdname
+ ": "
+ " ".join(tmp)
+ "'"
+ " requires at least four parameters"
)
raise ValueError
poles = int(tmp[0])
@@ -336,7 +449,9 @@ def process_multicmds(multicmds):
er_delta.append(float(tmp[pole]))
tau.append(float(tmp[pole + 1]))
debye_dispersion = AddDebyeDispersion(poles=poles, er_delta=er_delta, tau=tau, material_ids=material_ids)
debye_dispersion = AddDebyeDispersion(
poles=poles, er_delta=er_delta, tau=tau, material_ids=material_ids
)
scene_objects.append(debye_dispersion)
cmdname = "#add_dispersion_lorentz"
@@ -345,7 +460,14 @@ def process_multicmds(multicmds):
tmp = cmdinstance.split()
if len(tmp) < 5:
logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires at least five parameters")
logger.exception(
"'"
+ cmdname
+ ": "
+ " ".join(tmp)
+ "'"
+ " requires at least five parameters"
)
raise ValueError
poles = int(tmp[0])
@@ -360,7 +482,11 @@ def process_multicmds(multicmds):
alpha.append(float(tmp[pole + 2]))
lorentz_dispersion = AddLorentzDispersion(
poles=poles, material_ids=material_ids, er_delta=er_delta, tau=tau, alpha=alpha
poles=poles,
material_ids=material_ids,
er_delta=er_delta,
tau=tau,
alpha=alpha,
)
scene_objects.append(lorentz_dispersion)
@@ -370,7 +496,14 @@ def process_multicmds(multicmds):
tmp = cmdinstance.split()
if len(tmp) < 5:
logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires at least five parameters")
logger.exception(
"'"
+ cmdname
+ ": "
+ " ".join(tmp)
+ "'"
+ " requires at least five parameters"
)
raise ValueError
poles = int(tmp[0])
@@ -382,7 +515,9 @@ def process_multicmds(multicmds):
tau.append(float(tmp[pole]))
alpha.append(float(tmp[pole + 1]))
drude_dispersion = AddDrudeDispersion(poles=poles, material_ids=material_ids, tau=tau, alpha=alpha)
drude_dispersion = AddDrudeDispersion(
poles=poles, material_ids=material_ids, tau=tau, alpha=alpha
)
scene_objects.append(drude_dispersion)
cmdname = "#soil_peplinski"
@@ -391,7 +526,14 @@ def process_multicmds(multicmds):
tmp = cmdinstance.split()
if len(tmp) != 7:
logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires at exactly seven parameters")
logger.exception(
"'"
+ cmdname
+ ": "
+ " ".join(tmp)
+ "'"
+ " requires at exactly seven parameters"
)
raise ValueError
soil = SoilPeplinski(
sand_fraction=float(tmp[0]),
@@ -409,14 +551,23 @@ def process_multicmds(multicmds):
for cmdinstance in multicmds[cmdname]:
tmp = cmdinstance.split()
if len(tmp) != 11:
logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires exactly eleven parameters")
logger.exception(
"'"
+ cmdname
+ ": "
+ " ".join(tmp)
+ "'"
+ " requires exactly eleven parameters"
)
raise ValueError
p1 = float(tmp[0]), float(tmp[1]), float(tmp[2])
p2 = float(tmp[3]), float(tmp[4]), float(tmp[5])
dl = float(tmp[6]), float(tmp[7]), float(tmp[8])
geometry_view = GeometryView(p1=p1, p2=p2, dl=dl, filename=tmp[9], output_type=tmp[10])
geometry_view = GeometryView(
p1=p1, p2=p2, dl=dl, filename=tmp[9], output_type=tmp[10]
)
scene_objects.append(geometry_view)
cmdname = "#geometry_objects_write"
@@ -424,7 +575,14 @@ def process_multicmds(multicmds):
for cmdinstance in multicmds[cmdname]:
tmp = cmdinstance.split()
if len(tmp) != 7:
logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires exactly seven parameters")
logger.exception(
"'"
+ cmdname
+ ": "
+ " ".join(tmp)
+ "'"
+ " requires exactly seven parameters"
)
raise ValueError
p1 = float(tmp[0]), float(tmp[1]), float(tmp[2])
@@ -438,7 +596,14 @@ def process_multicmds(multicmds):
tmp = cmdinstance.split()
if len(tmp) != 9:
logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires at exactly nine parameters")
logger.exception(
"'"
+ cmdname
+ ": "
+ " ".join(tmp)
+ "'"
+ " requires at exactly nine parameters"
)
raise ValueError
material_range = MaterialRange(
er_lower=float(tmp[0]),
@@ -459,7 +624,14 @@ def process_multicmds(multicmds):
tmp = cmdinstance.split()
if len(tmp) < 2:
logger.exception("'" + cmdname + ": " + " ".join(tmp) + "'" + " requires at least 2 parameters")
logger.exception(
"'"
+ cmdname
+ ": "
+ " ".join(tmp)
+ "'"
+ " requires at least two parameters"
)
raise ValueError
tokens = len(tmp)
@@ -470,4 +642,37 @@ def process_multicmds(multicmds):
material_list = MaterialList(list_of_materials=lmats, id=tmp[tokens - 1])
scene_objects.append(material_list)
cmdname = "#pml_cfs"
if multicmds[cmdname] is not None:
for cmdinstance in multicmds[cmdname]:
tmp = cmdinstance.split()
if len(tmp) != 12:
logger.exception(
"'"
+ cmdname
+ ": "
+ " ".join(tmp)
+ "'"
+ " requires exactly twelve parameters"
)
raise ValueError
pml_cfs = PMLCFS(
alphascalingprofile=tmp[0],
alphascalingdirection=tmp[1],
alphamin=tmp[2],
alphamax=tmp[3],
kappascalingprofile=tmp[4],
kappascalingdirection=tmp[5],
kappamin=tmp[6],
kappamax=tmp[7],
sigmascalingprofile=tmp[8],
sigmascalingdirection=tmp[9],
sigmamin=tmp[10],
sigmamax=tmp[11],
)
scene_objects.append(pml_cfs)
return scene_objects

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -21,7 +21,6 @@ import logging
from .cmds_singleuse import (
Discretisation,
Domain,
ExcitationFile,
OMPThreads,
OutputDir,
PMLProps,
@@ -65,7 +64,7 @@ def process_singlecmds(singlecmds):
tmp = tuple(int(x) for x in singlecmds[cmd].split())
if len(tmp) != 1:
logger.exception(
f"{cmd} requires exactly one parameter to specify " + f"the number of CPU OpenMP threads to use"
f"{cmd} requires exactly one parameter to specify the number of CPU OpenMP threads to use"
)
raise ValueError
@@ -106,7 +105,7 @@ def process_singlecmds(singlecmds):
if len(tmp) != 1:
logger.exception(
f"{cmd} requires exactly one parameter to specify the "
+ f"time window. Either in seconds or number of iterations."
f"time window. Either in seconds or number of iterations."
)
raise ValueError
tmp = tmp[0].lower()
@@ -122,20 +121,49 @@ def process_singlecmds(singlecmds):
scene_objects.append(tw)
cmd = "#pml_formulation"
if singlecmds[cmd] is not None:
tmp = singlecmds[cmd].split()
if len(tmp) != 1:
logger.exception(f"{cmd} requires one parameter")
raise ValueError
else:
pml_formulation = tmp[0]
cmd = "#pml_cells"
if singlecmds[cmd] is not None:
tmp = singlecmds[cmd].split()
if len(tmp) not in [1, 6]:
logger.exception(f"{cmd} requires either one or six parameter(s)")
raise ValueError
if len(tmp) == 1:
pml_cells = PMLProps(thickness=int(tmp[0]))
else:
pml_cells = PMLProps(
x0=int(tmp[0]), y0=int(tmp[1]), z0=int(tmp[2]), xmax=int(tmp[3]), ymax=int(tmp[4]), zmax=int(tmp[5])
)
scene_objects.append(pml_cells)
if "pml_formulation" in locals():
if len(tmp) == 1:
pml_props = PMLProps(formulation=pml_formulation, thickness=int(tmp[0]))
else:
pml_props = PMLProps(
formulation=pml_formulation,
x0=int(tmp[0]),
y0=int(tmp[1]),
z0=int(tmp[2]),
xmax=int(tmp[3]),
ymax=int(tmp[4]),
zmax=int(tmp[5]),
)
else:
if len(tmp) == 1:
pml_props = PMLProps(thickness=int(tmp[0]))
else:
pml_props = PMLProps(
x0=int(tmp[0]),
y0=int(tmp[1]),
z0=int(tmp[2]),
xmax=int(tmp[3]),
ymax=int(tmp[4]),
zmax=int(tmp[5]),
)
scene_objects.append(pml_props)
cmd = "#src_steps"
if singlecmds[cmd] is not None:
@@ -159,19 +187,4 @@ def process_singlecmds(singlecmds):
rx_steps = RxSteps(p1=p1)
scene_objects.append(rx_steps)
# Excitation file for user-defined source waveforms
cmd = "#excitation_file"
if singlecmds[cmd] is not None:
tmp = singlecmds[cmd].split()
if len(tmp) not in [1, 3]:
logger.exception(f"{cmd} requires either one or three parameter(s)")
raise ValueError
if len(tmp) > 1:
ex_file = ExcitationFile(filepath=tmp[0], kind=tmp[1], fill_value=tmp[2])
else:
ex_file = ExcitationFile(filepath=tmp[0])
scene_objects.append(ex_file)
return scene_objects

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -168,7 +168,9 @@ class DispersiveMaterial(Material):
# tau for Lorentz materials are pole frequencies
# alpha for Lorentz materials are the damping coefficients
wp2 = (2 * np.pi * self.tau[x]) ** 2
self.w[x] = -1j * ((wp2 * self.deltaer[x]) / np.sqrt(wp2 - self.alpha[x] ** 2))
self.w[x] = -1j * (
(wp2 * self.deltaer[x]) / np.sqrt(wp2 - self.alpha[x] ** 2)
)
self.q[x] = -self.alpha[x] + (1j * np.sqrt(wp2 - self.alpha[x] ** 2))
elif "drude" in self.type:
# tau for Drude materials are pole frequencies
@@ -239,7 +241,15 @@ class PeplinskiSoil:
by Peplinski (http://dx.doi.org/10.1109/36.387598).
"""
def __init__(self, ID, sandfraction, clayfraction, bulkdensity, sandpartdensity, watervolfraction):
def __init__(
self,
ID,
sandfraction,
clayfraction,
bulkdensity,
sandpartdensity,
watervolfraction,
):
"""
Args:
ID: string for name of the soil.
@@ -278,10 +288,13 @@ class PeplinskiSoil:
watereri, waterer, watertau, watersig = calculate_water_properties(T, S)
f = 1.3e9
w = 2 * np.pi * f
erealw = watereri + ((waterer - watereri) / (1 + (w * watertau) ** 2))
waterdeltaer = waterer - watereri
erealw = watereri + (waterdeltaer / (1 + (w * watertau) ** 2))
a = 0.65 # Experimentally derived constant
es = (1.01 + 0.44 * self.rs) ** 2 - 0.062 #  Relative permittivity of sand particles
es = (
1.01 + 0.44 * self.rs
) ** 2 - 0.062 #  Relative permittivity of sand particles
b1 = 1.2748 - 0.519 * self.S - 0.152 * self.C
b2 = 1.33797 - 0.603 * self.S - 0.166 * self.C
@@ -303,36 +316,38 @@ class PeplinskiSoil:
muiter = np.nditer(mumaterials, flags=["c_index"])
while not muiter.finished:
# Real part for frequencies in the range 1.4GHz to 18GHz
er = (1 + (self.rb / self.rs) * ((es**a) - 1) + (muiter[0] ** b1 * erealw**a) - muiter[0]) ** (1 / a)
er = (
1
+ (self.rb / self.rs) * ((es**a) - 1)
+ (muiter[0] ** b1 * erealw**a)
- muiter[0]
) ** (1 / a)
# Real part for frequencies in the range 0.3GHz to 1.3GHz (linear
# correction to 1.4-18GHz value)
er = 1.15 * er - 0.68
# Permittivity at infinite frequency
eri = er - (muiter[0] ** (b2 / a) * DispersiveMaterial.waterdeltaer)
eri = er - (muiter[0] ** (b2 / a) * waterdeltaer)
# Effective conductivity
sig = muiter[0] ** (b2 / a) * ((sigf * (self.rs - self.rb)) / (self.rs * muiter[0]))
sig = muiter[0] ** (b2 / a) * (
(sigf * (self.rs - self.rb)) / (self.rs * muiter[0])
)
# Check to see if the material already exists before creating a new one
requiredID = "|{:.4f}|".format(float(muiter[0]))
material = next((x for x in G.materials if x.ID == requiredID), None)
if muiter.index == 0:
if material:
self.matID.append(material.numID)
if not material:
m = DispersiveMaterial(len(G.materials), requiredID)
m.type = "debye"
m.averagable = False
m.poles = 1
if m.poles > config.get_model_config().materials["maxpoles"]:
config.get_model_config().materials["maxpoles"] = m.poles
m.er = eri
m.se = sig
m.deltaer.append(er - eri)
m.tau.append(DispersiveMaterial.watertau)
G.materials.append(m)
self.matID.append(m.numID)
# Create individual materials
m = DispersiveMaterial(len(G.materials), None)
m.type = "debye"
m.averagable = False
m.poles = 1
if m.poles > config.get_model_config().materials["maxpoles"]:
config.get_model_config().materials["maxpoles"] = m.poles
m.er = eri
m.se = sig
m.deltaer.append(er - eri)
m.tau.append(watertau)
m.ID = f"|{float(m.er):.4f}+{float(m.se):.4f}+{float(m.mr):.4f}+{float(m.sm):.4f}|"
G.materials.append(m)
self.matID.append(m.numID)
muiter.iternext()
@@ -410,11 +425,12 @@ class RangeMaterial:
sm = romaterials[iter]
# Check to see if the material already exists before creating a new one
requiredID = f"|{float(er):.4f}+{float(se):.4f}+{float(mr):.4f}+{float(sm):.4f}|"
requiredID = (
f"|{float(er):.4f}+{float(se):.4f}+{float(mr):.4f}+{float(sm):.4f}|"
)
material = next((x for x in G.materials if x.ID == requiredID), None)
if iter == 0:
if material:
self.matID.append(material.numID)
if iter == 0 and material:
self.matID.append(material.numID)
if not material:
m = Material(len(G.materials), requiredID)
m.type = ""
@@ -461,7 +477,9 @@ class ListMaterial:
self.matID.append(material.numID)
if not material:
logger.exception(self.__str__() + f" material(s) {material} do not exist")
logger.exception(
self.__str__() + f" material(s) {material} do not exist"
)
raise ValueError
@@ -487,7 +505,7 @@ def calculate_water_properties(T=25, S=0):
"""Get extended Debye model properties for water.
Args:
T: float for emperature of water (degrees centigrade).
T: float for temperature of water (degrees centigrade).
S: float for salinity of water (part per thousand).
Returns:
@@ -500,11 +518,16 @@ def calculate_water_properties(T=25, S=0):
# Properties of water from: https://doi.org/10.1109/JOE.1977.1145319
eri = 4.9
er = 88.045 - 0.4147 * T + 6.295e-4 * T**2 + 1.075e-5 * T**3
tau = (1 / (2 * np.pi)) * (1.1109e-10 - 3.824e-12 * T + 6.938e-14 * T**2 - 5.096e-16 * T**3)
tau = (1 / (2 * np.pi)) * (
1.1109e-10 - 3.824e-12 * T + 6.938e-14 * T**2 - 5.096e-16 * T**3
)
delta = 25 - T
beta = (
2.033e-2 + 1.266e-4 * delta + 2.464e-6 * delta**2 - S * (1.849e-5 - 2.551e-7 * delta + 2.551e-8 * delta**2)
2.033e-2
+ 1.266e-4 * delta
+ 2.464e-6 * delta**2
- S * (1.849e-5 - 2.551e-7 * delta + 2.551e-8 * delta**2)
)
sig_25s = S * (0.182521 - 1.46192e-3 * S + 2.09324e-5 * S**2 - 1.28205e-7 * S**3)
sig = sig_25s * np.exp(-delta * beta)
@@ -613,8 +636,20 @@ def process_materials(G):
material.calculate_update_coeffsH(G)
# Add update coefficients to overall storage for all materials
G.updatecoeffsE[material.numID, :] = material.CA, material.CBx, material.CBy, material.CBz, material.srce
G.updatecoeffsH[material.numID, :] = material.DA, material.DBx, material.DBy, material.DBz, material.srcm
G.updatecoeffsE[material.numID, :] = (
material.CA,
material.CBx,
material.CBy,
material.CBz,
material.srce,
)
G.updatecoeffsH[material.numID, :] = (
material.DA,
material.DBx,
material.DBy,
material.DBz,
material.srcm,
)
# Add update coefficients to overall storage for dispersive materials
if hasattr(material, "poles"):
@@ -637,24 +672,30 @@ def process_materials(G):
]
if config.get_model_config().materials["maxpoles"] > 0:
if "debye" in material.type:
materialtext.append("\n".join("{:g}".format(deltaer) for deltaer in material.deltaer))
materialtext.append("\n".join("{:g}".format(tau) for tau in material.tau))
materialtext.append(
"\n".join(f"{deltaer:g}" for deltaer in material.deltaer)
)
materialtext.append("\n".join(f"{tau:g}" for tau in material.tau))
materialtext.extend(["", "", ""])
elif "lorentz" in material.type:
materialtext.append(", ".join("{:g}".format(deltaer) for deltaer in material.deltaer))
materialtext.append(
", ".join(f"{deltaer:g}" for deltaer in material.deltaer)
)
materialtext.append("")
materialtext.append(", ".join("{:g}".format(tau) for tau in material.tau))
materialtext.append(", ".join("{:g}".format(alpha) for alpha in material.alpha))
materialtext.append(", ".join(f"{tau:g}" for tau in material.tau))
materialtext.append(", ".join(f"{alpha:g}" for alpha in material.alpha))
materialtext.append("")
elif "drude" in material.type:
materialtext.extend(["", ""])
materialtext.append(", ".join("{:g}".format(tau) for tau in material.tau))
materialtext.append(", ".join(f"{tau:g}" for tau in material.tau))
materialtext.append("")
materialtext.append(", ".join("{:g}".format(alpha) for alpha in material.alpha))
materialtext.append(", ".join(f"{alpha:g}" for alpha in material.alpha))
else:
materialtext.extend(["", "", "", "", ""])
materialtext.extend((f"{material.mr:g}", f"{material.sm:g}", material.averagable))
materialtext.extend(
(f"{material.mr:g}", f"{material.sm:g}", material.averagable)
)
materialsdata.append(materialtext)
return materialsdata

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -58,9 +58,11 @@ class ModelBuildRun:
# Set number of OpenMP threads to physical threads at this point to be
# used with threaded model building methods, e.g. fractals. Can be
# changed by #num_threads command in input file or via API later for
# use with CPU solver.
config.get_model_config().ompthreads = set_omp_threads(config.get_model_config().ompthreads)
# changed by the user via #num_threads command in input file or via API
# later for use with CPU solver.
config.get_model_config().ompthreads = set_omp_threads(
config.get_model_config().ompthreads
)
def build(self):
"""Builds the Yee cells for a model."""
@@ -73,7 +75,9 @@ class ModelBuildRun:
# Normal model reading/building process; bypassed if geometry information to be reused
self.reuse_geometry() if config.get_model_config().reuse_geometry else self.build_geometry()
logger.info(f"\nOutput directory: {config.get_model_config().output_file_path.parent.resolve()}")
logger.info(
f"\nOutput directory: {config.get_model_config().output_file_path.parent.resolve()}"
)
# Adjust position of simple sources and receivers if required
if G.srcsteps[0] != 0 or G.srcsteps[1] != 0 or G.srcsteps[2] != 0:
@@ -81,13 +85,20 @@ class ModelBuildRun:
if config.model_num == 0:
if (
source.xcoord + G.srcsteps[0] * config.sim_config.model_end < 0
or source.xcoord + G.srcsteps[0] * config.sim_config.model_end > G.nx
or source.ycoord + G.srcsteps[1] * config.sim_config.model_end < 0
or source.ycoord + G.srcsteps[1] * config.sim_config.model_end > G.ny
or source.zcoord + G.srcsteps[2] * config.sim_config.model_end < 0
or source.zcoord + G.srcsteps[2] * config.sim_config.model_end > G.nz
or source.xcoord + G.srcsteps[0] * config.sim_config.model_end
> G.nx
or source.ycoord + G.srcsteps[1] * config.sim_config.model_end
< 0
or source.ycoord + G.srcsteps[1] * config.sim_config.model_end
> G.ny
or source.zcoord + G.srcsteps[2] * config.sim_config.model_end
< 0
or source.zcoord + G.srcsteps[2] * config.sim_config.model_end
> G.nz
):
logger.exception("Source(s) will be stepped to a position outside the domain.")
logger.exception(
"Source(s) will be stepped to a position outside the domain."
)
raise ValueError
source.xcoord = source.xcoordorigin + config.model_num * G.srcsteps[0]
source.ycoord = source.ycoordorigin + config.model_num * G.srcsteps[1]
@@ -97,21 +108,38 @@ class ModelBuildRun:
if config.model_num == 0:
if (
receiver.xcoord + G.rxsteps[0] * config.sim_config.model_end < 0
or receiver.xcoord + G.rxsteps[0] * config.sim_config.model_end > G.nx
or receiver.ycoord + G.rxsteps[1] * config.sim_config.model_end < 0
or receiver.ycoord + G.rxsteps[1] * config.sim_config.model_end > G.ny
or receiver.zcoord + G.rxsteps[2] * config.sim_config.model_end < 0
or receiver.zcoord + G.rxsteps[2] * config.sim_config.model_end > G.nz
or receiver.xcoord + G.rxsteps[0] * config.sim_config.model_end
> G.nx
or receiver.ycoord + G.rxsteps[1] * config.sim_config.model_end
< 0
or receiver.ycoord + G.rxsteps[1] * config.sim_config.model_end
> G.ny
or receiver.zcoord + G.rxsteps[2] * config.sim_config.model_end
< 0
or receiver.zcoord + G.rxsteps[2] * config.sim_config.model_end
> G.nz
):
logger.exception("Receiver(s) will be stepped to a position outside the domain.")
logger.exception(
"Receiver(s) will be stepped to a position outside the domain."
)
raise ValueError
receiver.xcoord = receiver.xcoordorigin + config.model_num * G.rxsteps[0]
receiver.ycoord = receiver.ycoordorigin + config.model_num * G.rxsteps[1]
receiver.zcoord = receiver.zcoordorigin + config.model_num * G.rxsteps[2]
receiver.xcoord = (
receiver.xcoordorigin + config.model_num * G.rxsteps[0]
)
receiver.ycoord = (
receiver.ycoordorigin + config.model_num * G.rxsteps[1]
)
receiver.zcoord = (
receiver.zcoordorigin + config.model_num * G.rxsteps[2]
)
# Write files for any geometry views and geometry object outputs
gvs = G.geometryviews + [gv for sg in G.subgrids for gv in sg.geometryviews]
if not gvs and not G.geometryobjectswrite and config.sim_config.args.geometry_only:
if (
not gvs
and not G.geometryobjectswrite
and config.sim_config.args.geometry_only
):
logger.exception("\nNo geometry views or geometry objects found.")
raise ValueError
save_geometry_views(gvs)
@@ -152,7 +180,11 @@ class ModelBuildRun:
for grid in grids:
if config.get_model_config().materials["maxpoles"] != 0:
config.get_model_config().materials["drudelorentz"] = any(
[m for m in grid.materials if "drude" in m.type or "lorentz" in m.type]
[
m
for m in grid.materials
if "drude" in m.type or "lorentz" in m.type
]
)
# Set data type if any dispersive materials (must be done before memory checks)
@@ -169,13 +201,13 @@ class ModelBuildRun:
if total_mem_build > total_mem_run:
logger.info(
f'\nMemory required (estimated): {" + ".join(mem_strs_build)} + '
f"\nMemory required (estimated): {' + '.join(mem_strs_build)} + "
f"~{humanize.naturalsize(config.get_model_config().mem_overhead)} "
f"overhead = {humanize.naturalsize(total_mem_build)}"
)
else:
logger.info(
f'\nMemory required (estimated): {" + ".join(mem_strs_run)} + '
f"\nMemory required (estimated): {' + '.join(mem_strs_run)} + "
f"~{humanize.naturalsize(config.get_model_config().mem_overhead)} "
f"overhead = {humanize.naturalsize(total_mem_run)}"
)
@@ -204,9 +236,13 @@ class ModelBuildRun:
results = dispersion_analysis(gb.grid)
if results["error"]:
logger.warning(
f"\nNumerical dispersion analysis [{gb.grid.name}] " f"not carried out as {results['error']}"
f"\nNumerical dispersion analysis [{gb.grid.name}] "
f"not carried out as {results['error']}"
)
elif results["N"] < config.get_model_config().numdispersion["mingridsampling"]:
elif (
results["N"]
< config.get_model_config().numdispersion["mingridsampling"]
):
logger.exception(
f"\nNon-physical wave propagation in [{gb.grid.name}] "
f"detected. Material '{results['material'].ID}' "
@@ -218,7 +254,8 @@ class ModelBuildRun:
raise ValueError
elif (
results["deltavp"]
and np.abs(results["deltavp"]) > config.get_model_config().numdispersion["maxnumericaldisp"]
and np.abs(results["deltavp"])
> config.get_model_config().numdispersion["maxnumericaldisp"]
):
logger.warning(
f"\n[{gb.grid.name}] has potentially significant "
@@ -246,7 +283,9 @@ class ModelBuildRun:
f"{config.sim_config.input_file_path}"
)
config.get_model_config().inputfilestr = (
Fore.GREEN + f"{s} {'-' * (get_terminal_width() - 1 - len(s))}\n" + Style.RESET_ALL
Fore.GREEN
+ f"{s} {'-' * (get_terminal_width() - 1 - len(s))}\n"
+ Style.RESET_ALL
)
logger.basic(config.get_model_config().inputfilestr)
for grid in [self.G] + self.G.subgrids:
@@ -274,8 +313,15 @@ class ModelBuildRun:
file(s).
"""
write_hdf5_outputfile(config.get_model_config().output_file_path_ext, self.G)
# Write output data to file if they are any receivers in any grids
sg_rxs = [True for sg in self.G.subgrids if sg.rxs]
sg_tls = [True for sg in self.G.subgrids if sg.transmissionlines]
if self.G.rxs or sg_rxs or self.G.transmissionlines or sg_tls:
write_hdf5_outputfile(
config.get_model_config().output_file_path_ext, self.G
)
# Write any snapshots to file for each grid
for grid in [self.G] + self.G.subgrids:
if grid.snapshots:
save_snapshots(grid)
@@ -294,7 +340,10 @@ class ModelBuildRun:
f"on {config.sim_config.hostinfo['hostname']} "
f"with OpenMP backend using {config.get_model_config().ompthreads} thread(s)"
)
if config.get_model_config().ompthreads > config.sim_config.hostinfo["physicalcores"]:
if (
config.get_model_config().ompthreads
> config.sim_config.hostinfo["physicalcores"]
):
logger.warning(
f"You have specified more threads ({config.get_model_config().ompthreads}) "
f"than available physical CPU cores ({config.sim_config.hostinfo['physicalcores']}). "
@@ -303,16 +352,28 @@ class ModelBuildRun:
elif config.sim_config.general["solver"] in ["cuda", "opencl"]:
if config.sim_config.general["solver"] == "opencl":
solvername = "OpenCL"
platformname = " on " + " ".join(config.get_model_config().device["dev"].platform.name.split())
devicename = " ".join(config.get_model_config().device["dev"].name.split())
platformname = (
" ".join(
config.get_model_config().device["dev"].platform.name.split()
)
+ " with "
)
devicename = (
f"Device {config.get_model_config().device['deviceID']}: "
f"{' '.join(config.get_model_config().device['dev'].name.split())}"
)
else:
solvername = "CUDA"
platformname = ""
devicename = " ".join(config.get_model_config().device["dev"].name().split())
devicename = (
f"Device {config.get_model_config().device['deviceID']}: "
f"{' '.join(config.get_model_config().device['dev'].name().split())}"
)
logger.basic(
f"\nModel {config.model_num + 1}/{config.sim_config.model_end} "
f"on {config.sim_config.hostinfo['hostname']} "
f"with {solvername} backend using {devicename}{platformname}"
f"solving on {config.sim_config.hostinfo['hostname']} "
f"with {solvername} backend using {platformname}{devicename}"
)
# Prepare iterator
@@ -334,16 +395,20 @@ class ModelBuildRun:
self.write_output_data()
# Print information about memory usage and solving time for a model
# Add a string on GPU memory usage if applicable
mem_str = (
f" host + ~{humanize.naturalsize(solver.memused)} GPU"
if config.sim_config.general["solver"] == "cuda"
else ""
)
# Add a string on device (GPU) memory usage if applicable
mem_str = ""
if config.sim_config.general["solver"] == "cuda":
mem_str = f" host + ~{humanize.naturalsize(solver.memused)} device"
elif config.sim_config.general["solver"] == "opencl":
mem_str = f" host + unknown for device"
logger.info(f"\nMemory used (estimated): " + f"~{humanize.naturalsize(self.p.memory_full_info().uss)}{mem_str}")
logger.info(
f"Time taken: " + f"{humanize.precisedelta(datetime.timedelta(seconds=solver.solvetime), format='%0.4f')}"
f"\nMemory used (estimated): "
+ f"~{humanize.naturalsize(self.p.memory_full_info().uss)}{mem_str}"
)
logger.info(
f"Time taken: "
+ f"{humanize.precisedelta(datetime.timedelta(seconds=solver.solvetime), format='%0.4f')}"
)
@@ -376,9 +441,13 @@ class GridBuilder:
file=sys.stdout,
disable=not config.sim_config.general["progressbars"],
)
build_electric_components(self.grid.solid, self.grid.rigidE, self.grid.ID, self.grid)
build_electric_components(
self.grid.solid, self.grid.rigidE, self.grid.ID, self.grid
)
pbar.update()
build_magnetic_components(self.grid.solid, self.grid.rigidH, self.grid.ID, self.grid)
build_magnetic_components(
self.grid.solid, self.grid.rigidH, self.grid.ID, self.grid
)
pbar.update()
pbar.close()

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Tobias Schruff
#
# This file is part of gprMax.
@@ -165,7 +165,9 @@ class MPIExecutor(object):
self.busy = [False] * len(self.workers)
if self.is_master():
logger.basic(f"\n({self.comm.name}) - Master: {self.master}, Workers: {self.workers}")
logger.basic(
f"\n({self.comm.name}) - Master: {self.master}, Workers: {self.workers}"
)
def __enter__(self):
"""Context manager enter. Only the master returns an executor, all other
@@ -222,7 +224,9 @@ class MPIExecutor(object):
"""Joins the workers."""
if not self.is_master():
return
logger.debug(f"({self.comm.name}) - Terminating. Sending sentinel to all workers.")
logger.debug(
f"({self.comm.name}) - Terminating. Sending sentinel to all workers."
)
# Send sentinel to all workers
for worker in self.workers:
self.comm.send(None, dest=worker, tag=Tags.EXIT)
@@ -269,7 +273,9 @@ class MPIExecutor(object):
for i, worker in enumerate(self.workers):
if self.comm.Iprobe(source=worker, tag=Tags.DONE):
job_idx, result = self.comm.recv(source=worker, tag=Tags.DONE)
logger.debug(f"({self.comm.name}) - Received finished job {job_idx} from worker {worker:d}.")
logger.debug(
f"({self.comm.name}) - Received finished job {job_idx} from worker {worker:d}."
)
results[job_idx] = result
self.busy[i] = False
elif self.comm.Iprobe(source=worker, tag=Tags.READY):
@@ -277,10 +283,16 @@ class MPIExecutor(object):
self.comm.recv(source=worker, tag=Tags.READY)
self.busy[i] = True
job_idx = num_jobs - len(my_jobs)
logger.debug(f"({self.comm.name}) - Sending job {job_idx} to worker {worker:d}.")
self.comm.send((job_idx, my_jobs.pop(0)), dest=worker, tag=Tags.START)
logger.debug(
f"({self.comm.name}) - Sending job {job_idx} to worker {worker:d}."
)
self.comm.send(
(job_idx, my_jobs.pop(0)), dest=worker, tag=Tags.START
)
elif self.comm.Iprobe(source=worker, tag=Tags.EXIT):
logger.debug(f"({self.comm.name}) - Worker on rank {worker:d} has terminated.")
logger.debug(
f"({self.comm.name}) - Worker on rank {worker:d} has terminated."
)
self.comm.recv(source=worker, tag=Tags.EXIT)
self.busy[i] = False
@@ -304,16 +316,22 @@ class MPIExecutor(object):
while True:
self.comm.send(None, dest=self.master, tag=Tags.READY)
logger.debug(f"({self.comm.name}) - Worker on rank {self.rank} waiting for job.")
logger.debug(
f"({self.comm.name}) - Worker on rank {self.rank} waiting for job."
)
data = self.comm.recv(source=self.master, tag=MPI.ANY_TAG, status=status)
tag = status.tag
if tag == Tags.START:
job_idx, work = data
logger.debug(f"({self.comm.name}) - Received job {job_idx} (work={work}).")
logger.debug(
f"({self.comm.name}) - Received job {job_idx} (work={work})."
)
result = self.__guarded_work(work)
logger.debug(f"({self.comm.name}) - Finished job. Sending results to master.")
logger.debug(
f"({self.comm.name}) - Finished job. Sending results to master."
)
self.comm.send((job_idx, result), dest=self.master, tag=Tags.DONE)
elif tag == Tags.EXIT:
logger.debug(f"({self.comm.name}) - Received sentinel from master.")

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with gprMax. If not, see <http://www.gnu.org/licenses/>.
import logging
from importlib import import_module
import numpy as np
@@ -24,6 +25,8 @@ import gprMax.config as config
from .cython.pml_build import pml_average_er_mr
logger = logging.getLogger(__name__)
class CFSParameter:
"""Individual CFS parameter (e.g. alpha, kappa, or sigma)."""
@@ -42,7 +45,15 @@ class CFSParameter:
}
scalingdirections = ["forward", "reverse"]
def __init__(self, ID=None, scaling="polynomial", scalingprofile=None, scalingdirection="forward", min=0, max=0):
def __init__(
self,
ID=None,
scaling="polynomial",
scalingprofile=None,
scalingdirection="forward",
min=0,
max=0,
):
"""
Args:
ID: string identifier for CFS parameter, can be: 'alpha', 'kappa' or
@@ -92,7 +103,9 @@ class CFS:
# Calculation of the maximum value of sigma from http://dx.doi.org/10.1109/8.546249
m = CFSParameter.scalingprofiles[self.sigma.scalingprofile]
self.sigma.max = (0.8 * (m + 1)) / (config.sim_config.em_consts["z0"] * d * np.sqrt(er * mr))
self.sigma.max = (0.8 * (m + 1)) / (
config.sim_config.em_consts["z0"] * d * np.sqrt(er * mr)
)
def scaling_polynomial(self, order, Evalues, Hvalues):
"""Applies the polynomial to be used for the scaling profile for
@@ -112,7 +125,10 @@ class CFS:
magnetic PML update.
"""
tmp = (np.linspace(0, (len(Evalues) - 1) + 0.5, num=2 * len(Evalues)) / (len(Evalues) - 1)) ** order
tmp = (
np.linspace(0, (len(Evalues) - 1) + 0.5, num=2 * len(Evalues))
/ (len(Evalues) - 1)
) ** order
Evalues = tmp[0:-1:2]
Hvalues = tmp[1::2]
@@ -135,8 +151,12 @@ class CFS:
# Extra cell of thickness added to allow correct scaling of electric and
# magnetic values
Evalues = np.zeros(thickness + 1, dtype=config.sim_config.dtypes["float_or_double"])
Hvalues = np.zeros(thickness + 1, dtype=config.sim_config.dtypes["float_or_double"])
Evalues = np.zeros(
thickness + 1, dtype=config.sim_config.dtypes["float_or_double"]
)
Hvalues = np.zeros(
thickness + 1, dtype=config.sim_config.dtypes["float_or_double"]
)
if parameter.scalingprofile == "constant":
Evalues += parameter.max
@@ -223,50 +243,76 @@ class PML:
self.thickness = self.nz
self.CFS = self.G.pmls["cfs"]
self.check_kappamin()
self.initialise_field_arrays()
def check_kappamin(self):
"""Checks that the sum of all kappamin values, i.e. when a multi-pole
PML is used, is not less than one.
"""
kappamin = sum(cfs.kappa.min for cfs in self.CFS)
if kappamin < 1:
logger.exception(
f"Sum of kappamin value(s) for PML is {kappamin} "
"and must be greater than one."
)
raise ValueError
def initialise_field_arrays(self):
"""Initialise arrays to store fields in PML."""
if self.direction[0] == "x":
self.EPhi1 = np.zeros(
(len(self.CFS), self.nx + 1, self.ny, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"]
(len(self.CFS), self.nx + 1, self.ny, self.nz + 1),
dtype=config.sim_config.dtypes["float_or_double"],
)
self.EPhi2 = np.zeros(
(len(self.CFS), self.nx + 1, self.ny + 1, self.nz), dtype=config.sim_config.dtypes["float_or_double"]
(len(self.CFS), self.nx + 1, self.ny + 1, self.nz),
dtype=config.sim_config.dtypes["float_or_double"],
)
self.HPhi1 = np.zeros(
(len(self.CFS), self.nx, self.ny + 1, self.nz), dtype=config.sim_config.dtypes["float_or_double"]
(len(self.CFS), self.nx, self.ny + 1, self.nz),
dtype=config.sim_config.dtypes["float_or_double"],
)
self.HPhi2 = np.zeros(
(len(self.CFS), self.nx, self.ny, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"]
(len(self.CFS), self.nx, self.ny, self.nz + 1),
dtype=config.sim_config.dtypes["float_or_double"],
)
elif self.direction[0] == "y":
self.EPhi1 = np.zeros(
(len(self.CFS), self.nx, self.ny + 1, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"]
(len(self.CFS), self.nx, self.ny + 1, self.nz + 1),
dtype=config.sim_config.dtypes["float_or_double"],
)
self.EPhi2 = np.zeros(
(len(self.CFS), self.nx + 1, self.ny + 1, self.nz), dtype=config.sim_config.dtypes["float_or_double"]
(len(self.CFS), self.nx + 1, self.ny + 1, self.nz),
dtype=config.sim_config.dtypes["float_or_double"],
)
self.HPhi1 = np.zeros(
(len(self.CFS), self.nx + 1, self.ny, self.nz), dtype=config.sim_config.dtypes["float_or_double"]
(len(self.CFS), self.nx + 1, self.ny, self.nz),
dtype=config.sim_config.dtypes["float_or_double"],
)
self.HPhi2 = np.zeros(
(len(self.CFS), self.nx, self.ny, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"]
(len(self.CFS), self.nx, self.ny, self.nz + 1),
dtype=config.sim_config.dtypes["float_or_double"],
)
elif self.direction[0] == "z":
self.EPhi1 = np.zeros(
(len(self.CFS), self.nx, self.ny + 1, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"]
(len(self.CFS), self.nx, self.ny + 1, self.nz + 1),
dtype=config.sim_config.dtypes["float_or_double"],
)
self.EPhi2 = np.zeros(
(len(self.CFS), self.nx + 1, self.ny, self.nz + 1), dtype=config.sim_config.dtypes["float_or_double"]
(len(self.CFS), self.nx + 1, self.ny, self.nz + 1),
dtype=config.sim_config.dtypes["float_or_double"],
)
self.HPhi1 = np.zeros(
(len(self.CFS), self.nx + 1, self.ny, self.nz), dtype=config.sim_config.dtypes["float_or_double"]
(len(self.CFS), self.nx + 1, self.ny, self.nz),
dtype=config.sim_config.dtypes["float_or_double"],
)
self.HPhi2 = np.zeros(
(len(self.CFS), self.nx, self.ny + 1, self.nz), dtype=config.sim_config.dtypes["float_or_double"]
(len(self.CFS), self.nx, self.ny + 1, self.nz),
dtype=config.sim_config.dtypes["float_or_double"],
)
def calculate_update_coeffs(self, er, mr):
@@ -277,14 +323,38 @@ class PML:
mr: float of average permeability of underlying material
"""
self.ERA = np.zeros((len(self.CFS), self.thickness), dtype=config.sim_config.dtypes["float_or_double"])
self.ERB = np.zeros((len(self.CFS), self.thickness), dtype=config.sim_config.dtypes["float_or_double"])
self.ERE = np.zeros((len(self.CFS), self.thickness), dtype=config.sim_config.dtypes["float_or_double"])
self.ERF = np.zeros((len(self.CFS), self.thickness), dtype=config.sim_config.dtypes["float_or_double"])
self.HRA = np.zeros((len(self.CFS), self.thickness), dtype=config.sim_config.dtypes["float_or_double"])
self.HRB = np.zeros((len(self.CFS), self.thickness), dtype=config.sim_config.dtypes["float_or_double"])
self.HRE = np.zeros((len(self.CFS), self.thickness), dtype=config.sim_config.dtypes["float_or_double"])
self.HRF = np.zeros((len(self.CFS), self.thickness), dtype=config.sim_config.dtypes["float_or_double"])
self.ERA = np.zeros(
(len(self.CFS), self.thickness),
dtype=config.sim_config.dtypes["float_or_double"],
)
self.ERB = np.zeros(
(len(self.CFS), self.thickness),
dtype=config.sim_config.dtypes["float_or_double"],
)
self.ERE = np.zeros(
(len(self.CFS), self.thickness),
dtype=config.sim_config.dtypes["float_or_double"],
)
self.ERF = np.zeros(
(len(self.CFS), self.thickness),
dtype=config.sim_config.dtypes["float_or_double"],
)
self.HRA = np.zeros(
(len(self.CFS), self.thickness),
dtype=config.sim_config.dtypes["float_or_double"],
)
self.HRB = np.zeros(
(len(self.CFS), self.thickness),
dtype=config.sim_config.dtypes["float_or_double"],
)
self.HRE = np.zeros(
(len(self.CFS), self.thickness),
dtype=config.sim_config.dtypes["float_or_double"],
)
self.HRF = np.zeros(
(len(self.CFS), self.thickness),
dtype=config.sim_config.dtypes["float_or_double"],
)
for x, cfs in enumerate(self.CFS):
if not cfs.sigma.max:
@@ -296,20 +366,30 @@ class PML:
# Define different parameters depending on PML formulation
if self.G.pmls["formulation"] == "HORIPML":
# HORIPML electric update coefficients
tmp = (2 * config.sim_config.em_consts["e0"] * Ekappa) + self.G.dt * (Ealpha * Ekappa + Esigma)
self.ERA[x, :] = (2 * config.sim_config.em_consts["e0"] + self.G.dt * Ealpha) / tmp
tmp = (2 * config.sim_config.em_consts["e0"] * Ekappa) + self.G.dt * (
Ealpha * Ekappa + Esigma
)
self.ERA[x, :] = (
2 * config.sim_config.em_consts["e0"] + self.G.dt * Ealpha
) / tmp
self.ERB[x, :] = (2 * config.sim_config.em_consts["e0"] * Ekappa) / tmp
self.ERE[x, :] = (
(2 * config.sim_config.em_consts["e0"] * Ekappa) - self.G.dt * (Ealpha * Ekappa + Esigma)
(2 * config.sim_config.em_consts["e0"] * Ekappa)
- self.G.dt * (Ealpha * Ekappa + Esigma)
) / tmp
self.ERF[x, :] = (2 * Esigma * self.G.dt) / (Ekappa * tmp)
# HORIPML magnetic update coefficients
tmp = (2 * config.sim_config.em_consts["e0"] * Hkappa) + self.G.dt * (Halpha * Hkappa + Hsigma)
self.HRA[x, :] = (2 * config.sim_config.em_consts["e0"] + self.G.dt * Halpha) / tmp
tmp = (2 * config.sim_config.em_consts["e0"] * Hkappa) + self.G.dt * (
Halpha * Hkappa + Hsigma
)
self.HRA[x, :] = (
2 * config.sim_config.em_consts["e0"] + self.G.dt * Halpha
) / tmp
self.HRB[x, :] = (2 * config.sim_config.em_consts["e0"] * Hkappa) / tmp
self.HRE[x, :] = (
(2 * config.sim_config.em_consts["e0"] * Hkappa) - self.G.dt * (Halpha * Hkappa + Hsigma)
(2 * config.sim_config.em_consts["e0"] * Hkappa)
- self.G.dt * (Halpha * Hkappa + Hsigma)
) / tmp
self.HRF[x, :] = (2 * Hsigma * self.G.dt) / (Hkappa * tmp)
@@ -318,14 +398,18 @@ class PML:
tmp = 2 * config.sim_config.em_consts["e0"] + self.G.dt * Ealpha
self.ERA[x, :] = Ekappa + (self.G.dt * Esigma) / tmp
self.ERB[x, :] = (2 * config.sim_config.em_consts["e0"]) / tmp
self.ERE[x, :] = ((2 * config.sim_config.em_consts["e0"]) - self.G.dt * Ealpha) / tmp
self.ERE[x, :] = (
(2 * config.sim_config.em_consts["e0"]) - self.G.dt * Ealpha
) / tmp
self.ERF[x, :] = (2 * Esigma * self.G.dt) / tmp
# MRIPML magnetic update coefficients
tmp = 2 * config.sim_config.em_consts["e0"] + self.G.dt * Halpha
self.HRA[x, :] = Hkappa + (self.G.dt * Hsigma) / tmp
self.HRB[x, :] = (2 * config.sim_config.em_consts["e0"]) / tmp
self.HRE[x, :] = ((2 * config.sim_config.em_consts["e0"]) - self.G.dt * Halpha) / tmp
self.HRE[x, :] = (
(2 * config.sim_config.em_consts["e0"]) - self.G.dt * Halpha
) / tmp
self.HRF[x, :] = (2 * Hsigma * self.G.dt) / tmp
def update_electric(self):
@@ -334,7 +418,10 @@ class PML:
"""
pmlmodule = "gprMax.cython.pml_updates_electric_" + self.G.pmls["formulation"]
func = getattr(import_module(pmlmodule), "order" + str(len(self.CFS)) + "_" + self.direction)
func = getattr(
import_module(pmlmodule),
"order" + str(len(self.CFS)) + "_" + self.direction,
)
func(
self.xs,
self.xf,
@@ -366,7 +453,10 @@ class PML:
"""
pmlmodule = "gprMax.cython.pml_updates_magnetic_" + self.G.pmls["formulation"]
func = getattr(import_module(pmlmodule), "order" + str(len(self.CFS)) + "_" + self.direction)
func = getattr(
import_module(pmlmodule),
"order" + str(len(self.CFS)) + "_" + self.direction,
)
func(
self.xs,
self.xf,
@@ -425,7 +515,11 @@ class CUDAPML(PML):
self.bpg = (
int(
np.ceil(
((self.EPhi1_dev.shape[1] + 1) * (self.EPhi1_dev.shape[2] + 1) * (self.EPhi1_dev.shape[3] + 1))
(
(self.EPhi1_dev.shape[1] + 1)
* (self.EPhi1_dev.shape[2] + 1)
* (self.EPhi1_dev.shape[3] + 1)
)
/ self.G.tpb[0]
)
),
@@ -433,19 +527,6 @@ class CUDAPML(PML):
1,
)
def get_update_funcs(self, kernelselectric, kernelsmagnetic):
"""Gets update functions from PML kernels.
Args:
kernelselectric: pycuda SourceModule containing PML kernels for
electric updates.
kernelsmagnetic: pycuda SourceModule containing PML kernels for
magnetic updates.
"""
self.update_electric_dev = kernelselectric.get_function("order" + str(len(self.CFS)) + "_" + self.direction)
self.update_magnetic_dev = kernelsmagnetic.get_function("order" + str(len(self.CFS)) + "_" + self.direction)
def update_electric(self):
"""Updates electric field components with the PML correction on the GPU."""
self.update_electric_dev(
@@ -549,12 +630,6 @@ class OpenCLPML(PML):
self.HPhi1_dev = clarray.to_device(self.queue, self.HPhi1)
self.HPhi2_dev = clarray.to_device(self.queue, self.HPhi2)
def set_blocks_per_grid():
pass
def get_update_funcs():
pass
def update_electric(self):
"""Updates electric field components with the PML correction on the
compute device.
@@ -636,7 +711,9 @@ def print_pml_info(G):
if all(value == 0 for value in G.pmls["thickness"].values()):
return f"\nPML boundaries [{G.name}]: switched off"
if all(value == G.pmls["thickness"]["x0"] for value in G.pmls["thickness"].values()):
if all(
value == G.pmls["thickness"]["x0"] for value in G.pmls["thickness"].values()
):
pmlinfo = str(G.pmls["thickness"]["x0"])
else:
pmlinfo = ""
@@ -678,31 +755,105 @@ def build_pml(G, pml_ID, thickness):
pml_type = OpenCLPML
if pml_ID == "x0":
pml = pml_type(G, ID=pml_ID, direction="xminus", xs=0, xf=thickness, ys=0, yf=G.ny, zs=0, zf=G.nz)
pml = pml_type(
G,
ID=pml_ID,
direction="xminus",
xs=0,
xf=thickness,
ys=0,
yf=G.ny,
zs=0,
zf=G.nz,
)
elif pml_ID == "xmax":
pml = pml_type(G, ID=pml_ID, direction="xplus", xs=G.nx - thickness, xf=G.nx, ys=0, yf=G.ny, zs=0, zf=G.nz)
pml = pml_type(
G,
ID=pml_ID,
direction="xplus",
xs=G.nx - thickness,
xf=G.nx,
ys=0,
yf=G.ny,
zs=0,
zf=G.nz,
)
elif pml_ID == "y0":
pml = pml_type(G, ID=pml_ID, direction="yminus", xs=0, xf=G.nx, ys=0, yf=thickness, zs=0, zf=G.nz)
pml = pml_type(
G,
ID=pml_ID,
direction="yminus",
xs=0,
xf=G.nx,
ys=0,
yf=thickness,
zs=0,
zf=G.nz,
)
elif pml_ID == "ymax":
pml = pml_type(G, ID=pml_ID, direction="yplus", xs=0, xf=G.nx, ys=G.ny - thickness, yf=G.ny, zs=0, zf=G.nz)
pml = pml_type(
G,
ID=pml_ID,
direction="yplus",
xs=0,
xf=G.nx,
ys=G.ny - thickness,
yf=G.ny,
zs=0,
zf=G.nz,
)
elif pml_ID == "z0":
pml = pml_type(G, ID=pml_ID, direction="zminus", xs=0, xf=G.nx, ys=0, yf=G.ny, zs=0, zf=thickness)
pml = pml_type(
G,
ID=pml_ID,
direction="zminus",
xs=0,
xf=G.nx,
ys=0,
yf=G.ny,
zs=0,
zf=thickness,
)
elif pml_ID == "zmax":
pml = pml_type(G, ID=pml_ID, direction="zplus", xs=0, xf=G.nx, ys=0, yf=G.ny, zs=G.nz - thickness, zf=G.nz)
pml = pml_type(
G,
ID=pml_ID,
direction="zplus",
xs=0,
xf=G.nx,
ys=0,
yf=G.ny,
zs=G.nz - thickness,
zf=G.nz,
)
if pml_ID[0] == "x":
averageer, averagemr = pml_average_er_mr(
G.ny, G.nz, config.get_model_config().ompthreads, G.solid[pml.xs, :, :], ers, mrs
G.ny,
G.nz,
config.get_model_config().ompthreads,
G.solid[pml.xs, :, :],
ers,
mrs,
)
elif pml_ID[0] == "y":
averageer, averagemr = pml_average_er_mr(
G.nx, G.nz, config.get_model_config().ompthreads, G.solid[:, pml.ys, :], ers, mrs
G.nx,
G.nz,
config.get_model_config().ompthreads,
G.solid[:, pml.ys, :],
ers,
mrs,
)
elif pml_ID[0] == "z":
averageer, averagemr = pml_average_er_mr(
G.nx, G.ny, config.get_model_config().ompthreads, G.solid[:, :, pml.zs], ers, mrs
G.nx,
G.ny,
config.get_model_config().ompthreads,
G.solid[:, :, pml.zs],
ers,
mrs,
)
pml.CFS = G.pmls["cfs"]
pml.calculate_update_coeffs(averageer, averagemr)
G.pmls["slabs"].append(pml)

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -26,9 +26,7 @@ class Rx:
allowableoutputs = ["Ex", "Ey", "Ez", "Hx", "Hy", "Hz", "Ix", "Iy", "Iz"]
defaultoutputs = allowableoutputs[:-3]
allowableoutputs_dev = allowableoutputs[:-3]
maxnumoutputs_dev = 0
def __init__(self):
self.ID = None
@@ -61,14 +59,12 @@ def htod_rx_arrays(G, queue=None):
rxcoords[i, 0] = rx.xcoord
rxcoords[i, 1] = rx.ycoord
rxcoords[i, 2] = rx.zcoord
# Store maximum number of output components
if len(rx.outputs) > Rx.maxnumoutputs_dev:
Rx.maxnumoutputs_dev = len(rx.outputs)
# Array to store field components for receivers on compute device -
# rows are field components; columns are iterations; pages are receivers
rxs = np.zeros(
(len(Rx.allowableoutputs_dev), G.iterations, len(G.rxs)), dtype=config.sim_config.dtypes["float_or_double"]
(len(Rx.allowableoutputs_dev), G.iterations, len(G.rxs)),
dtype=config.sim_config.dtypes["float_or_double"],
)
# Copy arrays to compute device
@@ -92,9 +88,9 @@ def dtoh_rx_array(rxs_dev, rxcoords_dev, G):
objects.
Args:
rxcoords_dev: int array of receiver coordinates on compute device.
rxs_dev: float array of receiver data on compute device - rows are field
components; columns are iterations; pages are receivers.
rxcoords_dev: int array of receiver coordinates on compute device.
G: FDTDGrid class describing a grid in a model.
"""
@@ -107,4 +103,6 @@ def dtoh_rx_array(rxs_dev, rxcoords_dev, G):
and rx.zcoord == rxcoords_dev[rxd, 2]
):
for output in rx.outputs.keys():
rx.outputs[output] = rxs_dev[Rx.allowableoutputs_dev.index(output), :, rxd]
rx.outputs[output] = rxs_dev[
Rx.allowableoutputs_dev.index(output), :, rxd
]

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -18,13 +18,16 @@
import logging
from .cmds_geometry.cmds_geometry import UserObjectGeometry
from .cmds_geometry.fractal_box_builder import FractalBoxBuilder
from .cmds_multiuse import UserObjectMulti
from .cmds_singleuse import Discretisation, Domain, TimeWindow, UserObjectSingle
from .materials import create_built_in_materials
from .subgrids.user_objects import SubGridBase as SubGridUserBase
from .user_inputs import create_user_input_points
from gprMax.cmds_geometry.add_grass import AddGrass
from gprMax.cmds_geometry.add_surface_roughness import AddSurfaceRoughness
from gprMax.cmds_geometry.add_surface_water import AddSurfaceWater
from gprMax.cmds_geometry.cmds_geometry import UserObjectGeometry
from gprMax.cmds_geometry.fractal_box import FractalBox
from gprMax.cmds_multiuse import UserObjectMulti
from gprMax.cmds_singleuse import Discretisation, Domain, TimeWindow, UserObjectSingle
from gprMax.materials import create_built_in_materials
from gprMax.subgrids.user_objects import SubGridBase as SubGridUserBase
from gprMax.user_inputs import create_user_input_points
logger = logging.getLogger(__name__)
@@ -55,8 +58,23 @@ class Scene:
logger.exception("This object is unknown to gprMax")
raise ValueError
def process_subgrid_commands(self):
# Check for subgrid user objects
def build_obj(self, obj, grid):
"""Builds objects.
Args:
obj: user object
grid: FDTDGrid class describing a grid in a model.
"""
uip = create_user_input_points(grid, obj)
try:
obj.build(grid, uip)
except ValueError:
logger.exception("Error creating user input object")
raise
def process_subgrid_cmds(self):
"""Process all commands in any sub-grids."""
def func(obj):
if isinstance(obj, SubGridUserBase):
return True
@@ -73,25 +91,32 @@ class Scene:
# to build in the correct subgrid.
sg = sg_cmd.subgrid
self.process_cmds(sg_cmd.children_multiple, sg)
self.process_cmds(sg_cmd.children_geometry, sg, sort=False)
def process_cmds(self, commands, grid, sort=True):
if sort:
cmds_sorted = sorted(commands, key=lambda cmd: cmd.order)
else:
cmds_sorted = commands
self.process_geocmds(sg_cmd.children_geometry, sg)
def process_cmds(self, commands, grid):
"""Process list of commands."""
cmds_sorted = sorted(commands, key=lambda cmd: cmd.order)
for obj in cmds_sorted:
# in the first level all objects belong to the main grid
uip = create_user_input_points(grid, obj)
# Create an instance to check the geometry points provided by the
# user. The way the point are checked depends on which grid the
# points belong to.
try:
obj.create(grid, uip)
except ValueError:
logger.exception("Error creating user input object")
raise
self.build_obj(obj, grid)
return self
def process_geocmds(self, commands, grid):
# Check for fractal boxes and modifications and pre-process them first
proc_cmds = []
for obj in commands:
if isinstance(
obj, (FractalBox, AddGrass, AddSurfaceRoughness, AddSurfaceWater)
):
self.build_obj(obj, grid)
if isinstance(obj, (FractalBox)):
proc_cmds.append(obj)
else:
proc_cmds.append(obj)
# Process all geometry commands
for obj in proc_cmds:
self.build_obj(obj, grid)
return self
@@ -116,16 +141,11 @@ class Scene:
self.process_cmds(cmds_unique, G)
def create_internal_objects(self, G):
"""Calls the UserObject.create() function in the correct way - API
"""Calls the UserObject.build() function in the correct way - API
presents the user with UserObjects in order to build the internal
Rx(), Cylinder() etc... objects.
"""
# Fractal box commands have an additional nonuser object which
# processes modifications
fbb = FractalBoxBuilder()
self.add(fbb)
# Create pre-defined (built-in) materials
create_built_in_materials(G)
@@ -140,7 +160,7 @@ class Scene:
grid.initialise_geometry_arrays()
# Process the main grid geometry commands
self.process_cmds(self.geometry_cmds, G, sort=False)
self.process_geocmds(self.geometry_cmds, G)
# Process all the commands for subgrids
self.process_subgrid_commands()
self.process_subgrid_cmds()

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -55,7 +55,9 @@ def save_snapshots(grid):
leave=True,
unit="byte",
unit_scale=True,
desc=f"Writing snapshot file {i + 1} " f"of {len(grid.snapshots)}, " f"{snap.filename.name}",
desc=f"Writing snapshot file {i + 1} "
f"of {len(grid.snapshots)}, "
f"{snap.filename.name}",
ncols=get_terminal_width() - 1,
file=sys.stdout,
disable=not config.sim_config.general["progressbars"],
@@ -68,7 +70,14 @@ def save_snapshots(grid):
class Snapshot:
"""Snapshots of the electric and magnetic field values."""
allowableoutputs = {"Ex": None, "Ey": None, "Ez": None, "Hx": None, "Hy": None, "Hz": None}
allowableoutputs = {
"Ex": None,
"Ey": None,
"Ez": None,
"Hx": None,
"Hy": None,
"Hz": None,
}
# Snapshots can be output as VTK ImageData (.vti) format or
# HDF5 format (.h5) files
@@ -136,13 +145,16 @@ class Snapshot:
for k, v in self.outputs.items():
if v:
self.snapfields[k] = np.zeros(
(self.nx, self.ny, self.nz), dtype=config.sim_config.dtypes["float_or_double"]
(self.nx, self.ny, self.nz),
dtype=config.sim_config.dtypes["float_or_double"],
)
self.nbytes += self.snapfields[k].nbytes
else:
# If output is not required for snapshot just use a mimimal
# size of array - still required to pass to Cython function
self.snapfields[k] = np.zeros((1, 1, 1), dtype=config.sim_config.dtypes["float_or_double"])
self.snapfields[k] = np.zeros(
(1, 1, 1), dtype=config.sim_config.dtypes["float_or_double"]
)
def store(self, G):
"""Store (in memory) electric and magnetic field values for snapshot.
@@ -208,26 +220,19 @@ class Snapshot:
G: FDTDGrid class describing a grid in a model.
"""
celldata = {}
for k, v in self.outputs.items():
if v:
if k == "Ex":
celldata[k] = self.snapfields["Ex"]
if k == "Ey":
celldata[k] = self.snapfields["Ey"]
if k == "Ez":
celldata[k] = self.snapfields["Ez"]
if k == "Hx":
celldata[k] = self.snapfields["Hx"]
if k == "Hy":
celldata[k] = self.snapfields["Hy"]
if k == "Hz":
celldata[k] = self.snapfields["Hz"]
celldata = {
k: self.snapfields[k]
for k in ["Ex", "Ey", "Ez", "Hx", "Hy", "Hz"]
if self.outputs.get(k)
}
imageToVTK(
str(self.filename.with_suffix("")),
origin=((self.xs * self.dx * G.dx), (self.ys * self.dy * G.dy), (self.zs * self.dz * G.dz)),
origin=(
(self.xs * self.dx * G.dx),
(self.ys * self.dy * G.dy),
(self.zs * self.dz * G.dz),
),
spacing=((self.dx * G.dx), (self.dy * G.dy), (self.dz * G.dz)),
cellData=celldata,
)
@@ -255,24 +260,10 @@ class Snapshot:
f.attrs["dx_dy_dz"] = (self.dx * G.dx, self.dy * G.dy, self.dz * G.dz)
f.attrs["time"] = self.time * G.dt
if self.outputs["Ex"]:
f["Ex"] = self.snapfields["Ex"]
pbar.update(n=self.snapfields["Ex"].nbytes)
if self.outputs["Ey"]:
f["Ey"] = self.snapfields["Ey"]
pbar.update(n=self.snapfields["Ey"].nbytes)
if self.outputs["Ez"]:
f["Ez"] = self.snapfields["Ez"]
pbar.update(n=self.snapfields["Ez"].nbytes)
if self.outputs["Hx"]:
f["Hx"] = self.snapfields["Hx"]
pbar.update(n=self.snapfields["Hx"].nbytes)
if self.outputs["Hy"]:
f["Hy"] = self.snapfields["Hy"]
pbar.update(n=self.snapfields["Hy"].nbytes)
if self.outputs["Hz"]:
f["Hz"] = self.snapfields["Hz"]
pbar.update(n=self.snapfields["Hz"].nbytes)
for key in ["Ex", "Ey", "Ez", "Hx", "Hy", "Hz"]:
if self.outputs[key]:
f[key] = self.snapfields[key]
pbar.update(n=self.snapfields[key].nbytes)
f.close()
@@ -300,35 +291,52 @@ def htod_snapshot_array(G, queue=None):
if config.sim_config.general["solver"] == "cuda":
# Blocks per grid - according to largest requested snapshot
Snapshot.bpg = (
int(np.ceil(((Snapshot.nx_max) * (Snapshot.ny_max) * (Snapshot.nz_max)) / Snapshot.tpb[0])),
int(
np.ceil(
((Snapshot.nx_max) * (Snapshot.ny_max) * (Snapshot.nz_max))
/ Snapshot.tpb[0]
)
),
1,
1,
)
elif config.sim_config.general["solver"] == "opencl":
# Workgroup size - according to largest requested snapshot
Snapshot.wgs = (int(np.ceil(((Snapshot.nx_max) * (Snapshot.ny_max) * (Snapshot.nz_max)))), 1, 1)
Snapshot.wgs = (
int(np.ceil(((Snapshot.nx_max) * (Snapshot.ny_max) * (Snapshot.nz_max)))),
1,
1,
)
# 4D arrays to store snapshots on GPU, e.g. snapEx(time, x, y, z);
# if snapshots are not being stored on the GPU during the simulation then
# they are copied back to the host after each iteration, hence numsnaps = 1
numsnaps = 1 if config.get_model_config().device["snapsgpu2cpu"] else len(G.snapshots)
numsnaps = (
1 if config.get_model_config().device["snapsgpu2cpu"] else len(G.snapshots)
)
snapEx = np.zeros(
(numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max), dtype=config.sim_config.dtypes["float_or_double"]
(numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max),
dtype=config.sim_config.dtypes["float_or_double"],
)
snapEy = np.zeros(
(numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max), dtype=config.sim_config.dtypes["float_or_double"]
(numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max),
dtype=config.sim_config.dtypes["float_or_double"],
)
snapEz = np.zeros(
(numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max), dtype=config.sim_config.dtypes["float_or_double"]
(numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max),
dtype=config.sim_config.dtypes["float_or_double"],
)
snapHx = np.zeros(
(numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max), dtype=config.sim_config.dtypes["float_or_double"]
(numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max),
dtype=config.sim_config.dtypes["float_or_double"],
)
snapHy = np.zeros(
(numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max), dtype=config.sim_config.dtypes["float_or_double"]
(numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max),
dtype=config.sim_config.dtypes["float_or_double"],
)
snapHz = np.zeros(
(numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max), dtype=config.sim_config.dtypes["float_or_double"]
(numsnaps, Snapshot.nx_max, Snapshot.ny_max, Snapshot.nz_max),
dtype=config.sim_config.dtypes["float_or_double"],
)
# Copy arrays to compute device
@@ -355,7 +363,9 @@ def htod_snapshot_array(G, queue=None):
return snapEx_dev, snapEy_dev, snapEz_dev, snapHx_dev, snapHy_dev, snapHz_dev
def dtoh_snapshot_array(snapEx_dev, snapEy_dev, snapEz_dev, snapHx_dev, snapHy_dev, snapHz_dev, i, snap):
def dtoh_snapshot_array(
snapEx_dev, snapEy_dev, snapEz_dev, snapHx_dev, snapHy_dev, snapHz_dev, i, snap
):
"""Copies snapshot array used on compute device back to snapshot objects and
store in format for Paraview.
@@ -365,9 +375,21 @@ def dtoh_snapshot_array(snapEx_dev, snapEy_dev, snapEz_dev, snapHx_dev, snapHy_d
snap: Snapshot class instance
"""
snap.snapfields["Ex"] = snapEx_dev[i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf]
snap.snapfields["Ey"] = snapEy_dev[i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf]
snap.snapfields["Ez"] = snapEz_dev[i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf]
snap.snapfields["Hx"] = snapHx_dev[i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf]
snap.snapfields["Hy"] = snapHy_dev[i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf]
snap.snapfields["Hz"] = snapHz_dev[i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf]
snap.snapfields["Ex"] = snapEx_dev[
i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf
]
snap.snapfields["Ey"] = snapEy_dev[
i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf
]
snap.snapfields["Ez"] = snapEz_dev[
i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf
]
snap.snapfields["Hx"] = snapHx_dev[
i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf
]
snap.snapfields["Hy"] = snapHy_dev[
i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf
]
snap.snapfields["Hz"] = snapHz_dev[
i, snap.xs : snap.xf, snap.ys : snap.yf, snap.zs : snap.zf
]

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.

223
gprMax/sources.py 普通文件 -> 可执行文件
查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -43,29 +43,10 @@ class Source:
self.start = None
self.stop = None
self.waveformID = None
def calculate_waveform_values(self, G):
"""Calculates all waveform values for source for duration of simulation.
Args:
G: FDTDGrid class describing a grid in a model.
"""
# Waveform values for sources that need to be calculated on whole timesteps
self.waveformvalues_wholedt = np.zeros((G.iterations), dtype=config.sim_config.dtypes["float_or_double"])
self.waveformvalues_wholedt = None
# Waveform values for sources that need to be calculated on half timesteps
self.waveformvalues_halfdt = np.zeros((G.iterations), dtype=config.sim_config.dtypes["float_or_double"])
waveform = next(x for x in G.waveforms if x.ID == self.waveformID)
for iteration in range(G.iterations):
time = G.dt * iteration
if time >= self.start and time <= self.stop:
# Set the time of the waveform evaluation to account for any
# delay in the start
time -= self.start
self.waveformvalues_wholedt[iteration] = waveform.calculate_value(time, G.dt)
self.waveformvalues_halfdt[iteration] = waveform.calculate_value(time + 0.5 * G.dt, G.dt)
self.waveformvalues_halfdt = None
class VoltageSource(Source):
@@ -79,6 +60,47 @@ class VoltageSource(Source):
super().__init__()
self.resistance = None
def calculate_waveform_values(self, G):
"""Calculates all waveform values for source for duration of simulation.
Args:
G: FDTDGrid class describing a grid in a model.
"""
# Check if a source matches existing source in terms of waveform and
# does not have a customised start/stop time. If so, use its
# pre-calculated waveform values, otherwise calculate them.
src_match = False
if self.start == 0 and self.stop == G.timewindow:
for src in G.voltagesources:
if src.waveformID == self.waveformID:
src_match = True
self.waveformvalues_halfdt = src.waveformvalues_halfdt
self.waveformvalues_wholedt = src.waveformvalues_wholedt
if not src_match:
waveform = next(x for x in G.waveforms if x.ID == self.waveformID)
self.waveformvalues_halfdt = np.zeros(
(G.iterations), dtype=config.sim_config.dtypes["float_or_double"]
)
self.waveformvalues_wholedt = np.zeros(
(G.iterations), dtype=config.sim_config.dtypes["float_or_double"]
)
for iteration in range(G.iterations):
time = G.dt * iteration
if time >= self.start and time <= self.stop:
# Set the time of the waveform evaluation to account for any
# delay in the start
time -= self.start
self.waveformvalues_halfdt[iteration] = waveform.calculate_value(
time + 0.5 * G.dt, G.dt
)
self.waveformvalues_wholedt[iteration] = waveform.calculate_value(
time, G.dt
)
def update_electric(self, iteration, updatecoeffsE, ID, Ex, Ey, Ez, G):
"""Updates electric field values for a voltage source.
@@ -149,7 +171,9 @@ class VoltageSource(Source):
newmaterial.ID = f"{material.ID}+{self.ID}"
newmaterial.numID = len(G.materials)
newmaterial.averagable = False
newmaterial.type += ",\nvoltage-source" if newmaterial.type else "voltage-source"
newmaterial.type += (
",\nvoltage-source" if newmaterial.type else "voltage-source"
)
# Add conductivity of voltage source to underlying conductivity
if self.polarisation == "x":
@@ -170,6 +194,40 @@ class HertzianDipole(Source):
super().__init__()
self.dl = None
def calculate_waveform_values(self, G):
"""Calculates all waveform values for source for duration of simulation.
Args:
G: FDTDGrid class describing a grid in a model.
"""
# Check if a source matches existing source in terms of waveform and
# does not have a customised start/stop time. If so, use its
# pre-calculated waveform values, otherwise calculate them.
src_match = False
if self.start == 0 and self.stop == G.timewindow:
for src in G.hertziandipoles:
if src.waveformID == self.waveformID:
src_match = True
self.waveformvalues_halfdt = src.waveformvalues_halfdt
if not src_match:
waveform = next(x for x in G.waveforms if x.ID == self.waveformID)
self.waveformvalues_halfdt = np.zeros(
(G.iterations), dtype=config.sim_config.dtypes["float_or_double"]
)
for iteration in range(G.iterations):
time = G.dt * iteration
if time >= self.start and time <= self.stop:
# Set the time of the waveform evaluation to account for any
# delay in the start
time -= self.start
self.waveformvalues_halfdt[iteration] = waveform.calculate_value(
time + 0.5 * G.dt, G.dt
)
def update_electric(self, iteration, updatecoeffsE, ID, Ex, Ey, Ez, G):
"""Updates electric field values for a Hertzian dipole.
@@ -216,6 +274,40 @@ class HertzianDipole(Source):
class MagneticDipole(Source):
"""A magnetic dipole is an additive source (magnetic current density)."""
def calculate_waveform_values(self, G):
"""Calculates all waveform values for source for duration of simulation.
Args:
G: FDTDGrid class describing a grid in a model.
"""
# Check if a source matches existing source in terms of waveform and
# does not have a customised start/stop time. If so, use its
# pre-calculated waveform values, otherwise calculate them.
src_match = False
if self.start == 0 and self.stop == G.timewindow:
for src in G.magneticdipoles:
if src.waveformID == self.waveformID:
src_match = True
self.waveformvalues_wholedt = src.waveformvalues_wholedt
if not src_match:
waveform = next(x for x in G.waveforms if x.ID == self.waveformID)
self.waveformvalues_wholedt = np.zeros(
(G.iterations), dtype=config.sim_config.dtypes["float_or_double"]
)
for iteration in range(G.iterations):
time = G.dt * iteration
if time >= self.start and time <= self.stop:
# Set the time of the waveform evaluation to account for any
# delay in the start
time -= self.start
self.waveformvalues_wholedt[iteration] = waveform.calculate_value(
time, G.dt
)
def update_magnetic(self, iteration, updatecoeffsH, ID, Hx, Hy, Hz, G):
"""Updates magnetic field values for a magnetic dipole.
@@ -275,8 +367,12 @@ def htod_src_arrays(sources, G, queue=None):
"""
srcinfo1 = np.zeros((len(sources), 4), dtype=np.int32)
srcinfo2 = np.zeros((len(sources)), dtype=config.sim_config.dtypes["float_or_double"])
srcwaves = np.zeros((len(sources), G.iterations), dtype=config.sim_config.dtypes["float_or_double"])
srcinfo2 = np.zeros(
(len(sources)), dtype=config.sim_config.dtypes["float_or_double"]
)
srcwaves = np.zeros(
(len(sources), G.iterations), dtype=config.sim_config.dtypes["float_or_double"]
)
for i, src in enumerate(sources):
srcinfo1[i, 0] = src.xcoord
srcinfo1[i, 1] = src.ycoord
@@ -353,12 +449,65 @@ class TransmissionLine(Source):
# Cell position of where line connects to antenna/main grid
self.antpos = 10
self.voltage = np.zeros(self.nl, dtype=config.sim_config.dtypes["float_or_double"])
self.current = np.zeros(self.nl, dtype=config.sim_config.dtypes["float_or_double"])
self.Vinc = np.zeros(G.iterations, dtype=config.sim_config.dtypes["float_or_double"])
self.Iinc = np.zeros(G.iterations, dtype=config.sim_config.dtypes["float_or_double"])
self.Vtotal = np.zeros(G.iterations, dtype=config.sim_config.dtypes["float_or_double"])
self.Itotal = np.zeros(G.iterations, dtype=config.sim_config.dtypes["float_or_double"])
self.voltage = np.zeros(
self.nl, dtype=config.sim_config.dtypes["float_or_double"]
)
self.current = np.zeros(
self.nl, dtype=config.sim_config.dtypes["float_or_double"]
)
self.Vinc = np.zeros(
G.iterations, dtype=config.sim_config.dtypes["float_or_double"]
)
self.Iinc = np.zeros(
G.iterations, dtype=config.sim_config.dtypes["float_or_double"]
)
self.Vtotal = np.zeros(
G.iterations, dtype=config.sim_config.dtypes["float_or_double"]
)
self.Itotal = np.zeros(
G.iterations, dtype=config.sim_config.dtypes["float_or_double"]
)
def calculate_waveform_values(self, G):
"""Calculates all waveform values for source for duration of simulation.
Args:
G: FDTDGrid class describing a grid in a model.
"""
# Check if a source matches existing source in terms of waveform and
# does not have a customised start/stop time. If so, use its
# pre-calculated waveform values, otherwise calculate them.
src_match = False
if self.start == 0 and self.stop == G.timewindow:
for src in G.transmissionlines:
if src.waveformID == self.waveformID:
src_match = True
self.waveformvalues_wholedt = src.waveformvalues_wholedt
self.waveformvalues_halfdt = src.waveformvalues_halfdt
if not src_match:
waveform = next(x for x in G.waveforms if x.ID == self.waveformID)
self.waveformvalues_wholedt = np.zeros(
(G.iterations), dtype=config.sim_config.dtypes["float_or_double"]
)
self.waveformvalues_halfdt = np.zeros(
(G.iterations), dtype=config.sim_config.dtypes["float_or_double"]
)
for iteration in range(G.iterations):
time = G.dt * iteration
if time >= self.start and time <= self.stop:
# Set the time of the waveform evaluation to account for any
# delay in the start
time -= self.start
self.waveformvalues_wholedt[iteration] = waveform.calculate_value(
time, G.dt
)
self.waveformvalues_halfdt[iteration] = waveform.calculate_value(
time + 0.5 * G.dt, G.dt
)
def calculate_incident_V_I(self, G):
"""Calculates the incident voltage and current with a long length
@@ -401,11 +550,15 @@ class TransmissionLine(Source):
# Update all the voltage values along the line
self.voltage[1 : self.nl] -= (
self.resistance * (config.c * G.dt / self.dl) * (self.current[1 : self.nl] - self.current[0 : self.nl - 1])
self.resistance
* (config.c * G.dt / self.dl)
* (self.current[1 : self.nl] - self.current[0 : self.nl - 1])
)
# Update the voltage at the position of the one-way injector excitation
self.voltage[self.srcpos] += (config.c * G.dt / self.dl) * self.waveformvalues_halfdt[iteration]
self.voltage[self.srcpos] += (
config.c * G.dt / self.dl
) * self.waveformvalues_halfdt[iteration]
# Update ABC before updating current
self.update_abc(G)
@@ -427,7 +580,9 @@ class TransmissionLine(Source):
# Update the current one cell before the position of the one-way injector excitation
self.current[self.srcpos - 1] += (
(1 / self.resistance) * (config.c * G.dt / self.dl) * self.waveformvalues_wholedt[iteration]
(1 / self.resistance)
* (config.c * G.dt / self.dl)
* self.waveformvalues_wholedt[iteration]
)
def update_electric(self, iteration, updatecoeffsE, ID, Ex, Ey, Ez, G):

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -233,7 +233,9 @@ class PrecursorNodesBase:
def interpolate_to_sub_grid(self, field, coords):
x, z, x_sg, z_sg = coords
interp_f = interpolate.RectBivariateSpline(x, z, field, kx=self.interpolation, ky=self.interpolation)
interp_f = interpolate.RectBivariateSpline(
x, z, field, kx=self.interpolation, ky=self.interpolation
)
f_i = interp_f(x_sg, z_sg)
return f_i
@@ -268,7 +270,7 @@ class PrecursorNodesBase:
# interpolate over a fine grid
f_i = self.interpolate_to_sub_grid(f_t, obj[1])
if f_i == f_t:
if np.array_equal(f_i, f_t):
raise ValueError
# discard the outer nodes only required for interpolation
@@ -417,18 +419,78 @@ class PrecursorNodes(PrecursorNodesBase):
# Spatially interpolate nodes
slices = [
["ex_front_1", True, (slice(i0, i1, 1), self.j0, slice(k0, k1 + 1, 1)), self.Ex],
["ex_back_1", True, (slice(i0, i1, 1), self.j1, slice(k0, k1 + 1, 1)), self.Ex],
["ez_front_1", False, (slice(i0, i1 + 1, 1), self.j0, slice(k0, k1, 1)), self.Ez],
["ez_back_1", False, (slice(i0, i1 + 1, 1), self.j1, slice(k0, k1, 1)), self.Ez],
["ey_left_1", True, (self.i0, slice(j0, j1, 1), slice(k0, k1 + 1, 1)), self.Ey],
["ey_right_1", True, (self.i1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)), self.Ey],
["ez_left_1", False, (self.i0, slice(j0, j1 + 1, 1), slice(k0, k1, 1)), self.Ez],
["ez_right_1", False, (self.i1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)), self.Ez],
["ex_bottom_1", True, (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k0), self.Ex],
["ex_top_1", True, (slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k1), self.Ex],
["ey_bottom_1", False, (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k0), self.Ey],
["ey_top_1", False, (slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k1), self.Ey],
[
"ex_front_1",
True,
(slice(i0, i1, 1), self.j0, slice(k0, k1 + 1, 1)),
self.Ex,
],
[
"ex_back_1",
True,
(slice(i0, i1, 1), self.j1, slice(k0, k1 + 1, 1)),
self.Ex,
],
[
"ez_front_1",
False,
(slice(i0, i1 + 1, 1), self.j0, slice(k0, k1, 1)),
self.Ez,
],
[
"ez_back_1",
False,
(slice(i0, i1 + 1, 1), self.j1, slice(k0, k1, 1)),
self.Ez,
],
[
"ey_left_1",
True,
(self.i0, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
self.Ey,
],
[
"ey_right_1",
True,
(self.i1, slice(j0, j1, 1), slice(k0, k1 + 1, 1)),
self.Ey,
],
[
"ez_left_1",
False,
(self.i0, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
self.Ez,
],
[
"ez_right_1",
False,
(self.i1, slice(j0, j1 + 1, 1), slice(k0, k1, 1)),
self.Ez,
],
[
"ex_bottom_1",
True,
(slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k0),
self.Ex,
],
[
"ex_top_1",
True,
(slice(i0, i1, 1), slice(j0, j1 + 1, 1), self.k1),
self.Ex,
],
[
"ey_bottom_1",
False,
(slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k0),
self.Ey,
],
[
"ey_top_1",
False,
(slice(i0, i1 + 1, 1), slice(j0, j1, 1), self.k1),
self.Ey,
],
]
for obj in slices:

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -20,7 +20,11 @@ import logging
import gprMax.config as config
from ..cython.fields_updates_hsg import update_electric_os, update_is, update_magnetic_os
from ..cython.fields_updates_hsg import (
update_electric_os,
update_is,
update_magnetic_os,
)
from .grid import SubGridBaseGrid
logger = logging.getLogger(__name__)
@@ -673,16 +677,27 @@ class SubGridHSG(SubGridBaseGrid):
# Working region
xs, ys, zs = self.round_to_grid(
(self.i0 * self.dx * self.ratio, self.j0 * self.dy * self.ratio, self.k0 * self.dz * self.ratio)
(
self.i0 * self.dx * self.ratio,
self.j0 * self.dy * self.ratio,
self.k0 * self.dz * self.ratio,
)
)
xf, yf, zf = self.round_to_grid(
(self.i1 * self.dx * self.ratio, self.j1 * self.dy * self.ratio, self.k1 * self.dz * self.ratio)
(
self.i1 * self.dx * self.ratio,
self.j1 * self.dy * self.ratio,
self.k1 * self.dz * self.ratio,
)
)
logger.info("")
logger.debug(f"[{self.name}] Type: {self.__class__.__name__}")
logger.info(f"[{self.name}] Ratio: 1:{self.ratio}")
logger.info(f"[{self.name}] Spatial discretisation: {self.dx:g} x " + f"{self.dy:g} x {self.dz:g}m")
logger.info(
f"[{self.name}] Spatial discretisation: {self.dx:g} x "
+ f"{self.dy:g} x {self.dz:g}m"
)
logger.info(
f"[{self.name}] Extent (working region): {xs}m, {ys}m, {zs}m to {xf}m, {yf}m, {zf}m "
+ f"(({self.nwx} x {self.nwy} x {self.nwz} = {self.nwx * self.nwy * self.nwz} cells)"

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -121,7 +121,7 @@ class SubGridBase(UserObjectMulti):
sg.timewindow = grid.timewindow
# Copy a subgrid reference to self so that children.create(grid, uip)
# Copy a subgrid reference to self so that children.build(grid, uip)
# can access the correct grid.
self.subgrid = sg
@@ -131,7 +131,9 @@ class SubGridBase(UserObjectMulti):
# Don't mix and match different subgrid types
for sg_made in grid.subgrids:
if type(sg) != type(sg_made):
logger.exception(f"{self.__str__()} please only use one type of subgrid")
logger.exception(
f"{self.__str__()} please only use one type of subgrid"
)
raise ValueError
# Reference the subgrid under the main grid to which it belongs
@@ -192,7 +194,7 @@ class SubGridHSG(SubGridBase):
self.order = 18
self.hash = "#subgrid_hsg"
def create(self, grid, uip):
def build(self, grid, uip):
sg = SubGridHSGUser(**self.kwargs)
self.setup(sg, grid, uip)
return sg

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -19,12 +19,18 @@
import logging
from importlib import import_module
import humanize
import numpy as np
from jinja2 import Environment, PackageLoader
import gprMax.config as config
from .cuda_opencl import knl_fields_updates, knl_snapshots, knl_source_updates, knl_store_outputs
from .cuda_opencl import (
knl_fields_updates,
knl_snapshots,
knl_source_updates,
knl_store_outputs,
)
from .cython.fields_updates_normal import update_electric as update_electric_cpu
from .cython.fields_updates_normal import update_magnetic as update_magnetic_cpu
from .fields_outputs import store_outputs as store_outputs_cpu
@@ -44,9 +50,8 @@ class CPUUpdates:
Args:
G: FDTDGrid class describing a grid in a model.
"""
self.grid = G
self.dispersive_update_a = None
self.dispersive_update_b = None
def store_outputs(self):
"""Stores field component values for every receiver and transmission line."""
@@ -170,7 +175,11 @@ class CPUUpdates:
"""Updates electric field components from sources -
update any Hertzian dipole sources last.
"""
for source in self.grid.voltagesources + self.grid.transmissionlines + self.grid.hertziandipoles:
for source in (
self.grid.voltagesources
+ self.grid.transmissionlines
+ self.grid.hertziandipoles
):
source.update_electric(
self.grid.iteration,
self.grid.updatecoeffsE,
@@ -189,7 +198,7 @@ class CPUUpdates:
updated after the electric field has been updated by the PML and
source updates.
"""
if config.get_model_config().materials["maxpoles"] != 0:
if config.get_model_config().materials["maxpoles"] > 0:
self.dispersive_update_b(
self.grid.nx,
self.grid.ny,
@@ -210,10 +219,13 @@ class CPUUpdates:
"""Sets dispersive update functions."""
poles = "multi" if config.get_model_config().materials["maxpoles"] > 1 else "1"
precision = "float" if config.sim_config.general["precision"] == "single" else "double"
precision = (
"float" if config.sim_config.general["precision"] == "single" else "double"
)
dispersion = (
"complex"
if config.get_model_config().materials["dispersivedtype"] == config.sim_config.dtypes["complex"]
if config.get_model_config().materials["dispersivedtype"]
== config.sim_config.dtypes["complex"]
else "real"
)
@@ -221,8 +233,12 @@ class CPUUpdates:
disp_a = update_f.format(poles, "A", precision, dispersion)
disp_b = update_f.format(poles, "B", precision, dispersion)
disp_a_f = getattr(import_module("gprMax.cython.fields_updates_dispersive"), disp_a)
disp_b_f = getattr(import_module("gprMax.cython.fields_updates_dispersive"), disp_b)
disp_a_f = getattr(
import_module("gprMax.cython.fields_updates_dispersive"), disp_a
)
disp_b_f = getattr(
import_module("gprMax.cython.fields_updates_dispersive"), disp_b
)
self.dispersive_update_a = disp_a_f
self.dispersive_update_b = disp_b_f
@@ -252,8 +268,6 @@ class CUDAUpdates:
"""
self.grid = G
self.dispersive_update_a = None
self.dispersive_update_b = None
# Import PyCUDA modules
self.drv = import_module("pycuda.driver")
@@ -292,7 +306,11 @@ class CUDAUpdates:
self._set_pml_knls()
if self.grid.rxs:
self._set_rx_knl()
if self.grid.voltagesources + self.grid.hertziandipoles + self.grid.magneticdipoles:
if (
self.grid.voltagesources
+ self.grid.hertziandipoles
+ self.grid.magneticdipoles
):
self._set_src_knls()
if self.grid.snapshots:
self._set_snapshot_knl()
@@ -365,11 +383,15 @@ class CUDAUpdates:
gets kernel functions.
"""
bld = self._build_knl(knl_fields_updates.update_electric, self.subs_name_args, self.subs_func)
bld = self._build_knl(
knl_fields_updates.update_electric, self.subs_name_args, self.subs_func
)
knlE = self.source_module(bld, options=config.sim_config.devices["nvcc_opts"])
self.update_electric_dev = knlE.get_function("update_electric")
bld = self._build_knl(knl_fields_updates.update_magnetic, self.subs_name_args, self.subs_func)
bld = self._build_knl(
knl_fields_updates.update_magnetic, self.subs_name_args, self.subs_func
)
knlH = self.source_module(bld, options=config.sim_config.devices["nvcc_opts"])
self.update_magnetic_dev = knlH.get_function("update_magnetic")
@@ -381,20 +403,34 @@ class CUDAUpdates:
self.subs_func.update(
{
"REAL": config.sim_config.dtypes["C_float_or_double"],
"REALFUNC": config.get_model_config().materials["cudarealfunc"],
"REALFUNC": config.get_model_config().materials["crealfunc"],
"NX_T": self.grid.Tx.shape[1],
"NY_T": self.grid.Tx.shape[2],
"NZ_T": self.grid.Tx.shape[3],
}
)
bld = self._build_knl(knl_fields_updates.update_electric_dispersive_A, self.subs_name_args, self.subs_func)
knl = self.source_module(bld, options=config.sim_config.devices["nvcc_opts"])
bld = self._build_knl(
knl_fields_updates.update_electric_dispersive_A,
self.subs_name_args,
self.subs_func,
)
knl = self.source_module(
bld, options=config.sim_config.devices["nvcc_opts"]
)
self.dispersive_update_a = knl.get_function("update_electric_dispersive_A")
self._copy_mat_coeffs(knl, knl)
bld = self._build_knl(knl_fields_updates.update_electric_dispersive_B, self.subs_name_args, self.subs_func)
knl = self.source_module(bld, options=config.sim_config.devices["nvcc_opts"])
bld = self._build_knl(
knl_fields_updates.update_electric_dispersive_B,
self.subs_name_args,
self.subs_func,
)
knl = self.source_module(
bld, options=config.sim_config.devices["nvcc_opts"]
)
self.dispersive_update_b = knl.get_function("update_electric_dispersive_B")
self._copy_mat_coeffs(knl, knl)
# Set blocks per grid and initialise field arrays on GPU
self.grid.set_blocks_per_grid()
@@ -406,10 +442,12 @@ class CUDAUpdates:
def _set_pml_knls(self):
"""PMLS - prepares kernels and gets kernel functions."""
knl_pml_updates_electric = import_module(
"gprMax.cuda_opencl.knl_pml_updates_electric_" + self.grid.pmls["formulation"]
"gprMax.cuda_opencl.knl_pml_updates_electric_"
+ self.grid.pmls["formulation"]
)
knl_pml_updates_magnetic = import_module(
"gprMax.cuda_opencl.knl_pml_updates_magnetic_" + self.grid.pmls["formulation"]
"gprMax.cuda_opencl.knl_pml_updates_magnetic_"
+ self.grid.pmls["formulation"]
)
# Initialise arrays on GPU, set block per grid, and get kernel functions
@@ -421,15 +459,21 @@ class CUDAUpdates:
knl_electric = getattr(knl_pml_updates_electric, knl_name)
bld = self._build_knl(knl_electric, self.subs_name_args, self.subs_func)
knlE = self.source_module(bld, options=config.sim_config.devices["nvcc_opts"])
knlE = self.source_module(
bld, options=config.sim_config.devices["nvcc_opts"]
)
pml.update_electric_dev = knlE.get_function(knl_name)
knl_magnetic = getattr(knl_pml_updates_magnetic, knl_name)
bld = self._build_knl(knl_magnetic, self.subs_name_args, self.subs_func)
knlH = self.source_module(bld, options=config.sim_config.devices["nvcc_opts"])
knlH = self.source_module(
bld, options=config.sim_config.devices["nvcc_opts"]
)
pml.update_magnetic_dev = knlH.get_function(knl_name)
self._copy_mat_coeffs(knlE, knlH)
# Copy material coefficient arrays to constant memory of GPU - must
# be done for each kernel
self._copy_mat_coeffs(knlE, knlH)
def _set_rx_knl(self):
"""Receivers - initialises arrays on GPU, prepares kernel and gets kernel
@@ -447,7 +491,9 @@ class CUDAUpdates:
}
)
bld = self._build_knl(knl_store_outputs.store_outputs, self.subs_name_args, self.subs_func)
bld = self._build_knl(
knl_store_outputs.store_outputs, self.subs_name_args, self.subs_func
)
knl = self.source_module(bld, options=config.sim_config.devices["nvcc_opts"])
self.store_outputs_dev = knl.get_function("store_outputs")
@@ -458,25 +504,49 @@ class CUDAUpdates:
self.subs_func.update({"NY_SRCINFO": 4, "NY_SRCWAVES": self.grid.iteration})
if self.grid.hertziandipoles:
self.srcinfo1_hertzian_dev, self.srcinfo2_hertzian_dev, self.srcwaves_hertzian_dev = htod_src_arrays(
self.grid.hertziandipoles, self.grid
(
self.srcinfo1_hertzian_dev,
self.srcinfo2_hertzian_dev,
self.srcwaves_hertzian_dev,
) = htod_src_arrays(self.grid.hertziandipoles, self.grid)
bld = self._build_knl(
knl_source_updates.update_hertzian_dipole,
self.subs_name_args,
self.subs_func,
)
knl = self.source_module(
bld, options=config.sim_config.devices["nvcc_opts"]
)
bld = self._build_knl(knl_source_updates.update_hertzian_dipole, self.subs_name_args, self.subs_func)
knl = self.source_module(bld, options=config.sim_config.devices["nvcc_opts"])
self.update_hertzian_dipole_dev = knl.get_function("update_hertzian_dipole")
if self.grid.magneticdipoles:
self.srcinfo1_magnetic_dev, self.srcinfo2_magnetic_dev, self.srcwaves_magnetic_dev = htod_src_arrays(
self.grid.magneticdipoles, self.grid
(
self.srcinfo1_magnetic_dev,
self.srcinfo2_magnetic_dev,
self.srcwaves_magnetic_dev,
) = htod_src_arrays(self.grid.magneticdipoles, self.grid)
bld = self._build_knl(
knl_source_updates.update_magnetic_dipole,
self.subs_name_args,
self.subs_func,
)
knl = self.source_module(
bld, options=config.sim_config.devices["nvcc_opts"]
)
bld = self._build_knl(knl_source_updates.update_magnetic_dipole, self.subs_name_args, self.subs_func)
knl = self.source_module(bld, options=config.sim_config.devices["nvcc_opts"])
self.update_magnetic_dipole_dev = knl.get_function("update_magnetic_dipole")
if self.grid.voltagesources:
self.srcinfo1_voltage_dev, self.srcinfo2_voltage_dev, self.srcwaves_voltage_dev = htod_src_arrays(
self.grid.voltagesources, self.grid
(
self.srcinfo1_voltage_dev,
self.srcinfo2_voltage_dev,
self.srcwaves_voltage_dev,
) = htod_src_arrays(self.grid.voltagesources, self.grid)
bld = self._build_knl(
knl_source_updates.update_voltage_source,
self.subs_name_args,
self.subs_func,
)
knl = self.source_module(
bld, options=config.sim_config.devices["nvcc_opts"]
)
bld = self._build_knl(knl_source_updates.update_voltage_source, self.subs_name_args, self.subs_func)
knl = self.source_module(bld, options=config.sim_config.devices["nvcc_opts"])
self.update_voltage_source_dev = knl.get_function("update_voltage_source")
self._copy_mat_coeffs(knl, knl)
@@ -503,7 +573,9 @@ class CUDAUpdates:
}
)
bld = self._build_knl(knl_snapshots.store_snapshot, self.subs_name_args, self.subs_func)
bld = self._build_knl(
knl_snapshots.store_snapshot, self.subs_name_args, self.subs_func
)
knl = self.source_module(bld, options=config.sim_config.devices["nvcc_opts"])
self.store_snapshot_dev = knl.get_function("store_snapshot")
@@ -804,9 +876,9 @@ class CUDAUpdates:
def cleanup(self):
"""Cleanup GPU context."""
# Remove context from top of stack and delete
# Remove context from top of stack and clear
self.ctx.pop()
del self.ctx
self.ctx = None
class OpenCLUpdates:
@@ -819,36 +891,43 @@ class OpenCLUpdates:
"""
self.grid = G
self.dispersive_update_a = None
self.dispersive_update_b = None
# Import pyopencl module
self.cl = import_module("pyopencl")
self.elwise = getattr(import_module("pyopencl.elementwise"), "ElementwiseKernel")
self.elwiseknl = getattr(
import_module("pyopencl.elementwise"), "ElementwiseKernel"
)
# Select device, create context and command queue
self.dev = config.get_model_config().device["dev"]
self.ctx = self.cl.Context(devices=[self.dev])
self.queue = self.cl.CommandQueue(self.ctx, properties=self.cl.command_queue_properties.PROFILING_ENABLE)
self.queue = self.cl.CommandQueue(
self.ctx, properties=self.cl.command_queue_properties.PROFILING_ENABLE
)
# Enviroment for templating kernels
self.env = Environment(loader=PackageLoader("gprMax", "cuda_opencl"))
# Initialise arrays on device, prepare kernels, and get kernel functions
self._set_macros()
self._set_field_knls()
if self.grid.pmls["slabs"]:
self._set_pml_knls()
if self.grid.rxs:
self._set_rx_knl()
if self.grid.voltagesources + self.grid.hertziandipoles + self.grid.magneticdipoles:
if (
self.grid.voltagesources
+ self.grid.hertziandipoles
+ self.grid.magneticdipoles
):
self._set_src_knls()
if self.grid.snapshots:
self._set_snapshot_knl()
def _set_field_knls(self):
"""Electric and magnetic field updates - prepares kernels, and
gets kernel functions.
"""
def _set_macros(self):
"""Common macros to be used in kernels."""
# Set specific values for any dispersive materials
if config.get_model_config().materials["maxpoles"] > 0:
NY_MATDISPCOEFFS = self.grid.updatecoeffsdispersive.shape[1]
NX_T = self.grid.Tx.shape[1]
@@ -888,6 +967,11 @@ class OpenCLUpdates:
NZ_SNAPS=Snapshot.nz_max,
)
def _set_field_knls(self):
"""Electric and magnetic field updates - prepares kernels, and
gets kernel functions.
"""
subs = {
"CUDA_IDX": "",
"NX_FIELDS": self.grid.nx + 1,
@@ -898,7 +982,7 @@ class OpenCLUpdates:
"NZ_ID": self.grid.ID.shape[3],
}
self.update_electric_dev = self.elwise(
self.update_electric_dev = self.elwiseknl(
self.ctx,
knl_fields_updates.update_electric["args_opencl"].substitute(
{"REAL": config.sim_config.dtypes["C_float_or_double"]}
@@ -909,7 +993,7 @@ class OpenCLUpdates:
options=config.sim_config.devices["compiler_opts"],
)
self.update_magnetic_dev = self.elwise(
self.update_magnetic_dev = self.elwiseknl(
self.ctx,
knl_fields_updates.update_magnetic["args_opencl"].substitute(
{"REAL": config.sim_config.dtypes["C_float_or_double"]}
@@ -933,32 +1017,46 @@ class OpenCLUpdates:
"NX_ID": self.grid.ID.shape[1],
"NY_ID": self.grid.ID.shape[2],
"NZ_ID": self.grid.ID.shape[3],
"NX_T": NX_T,
"NY_T": NY_T,
"NZ_T": NZ_T,
"NX_T": self.grid.Tx.shape[1],
"NY_T": self.grid.Tx.shape[2],
"NZ_T": self.grid.Tx.shape[3],
}
self.dispersive_update_a = self.elwise(
self.dispersive_update_a = self.elwiseknl(
self.ctx,
knl_fields_updates.update_electric_dispersive_A["args_opencl"].substitute(
knl_fields_updates.update_electric_dispersive_A[
"args_opencl"
].substitute(
{
"REAL": config.sim_config.dtypes["C_float_or_double"],
"COMPLEX": config.get_model_config().materials["dispersiveCdtype"],
"COMPLEX": config.get_model_config().materials[
"dispersiveCdtype"
],
}
),
knl_fields_updates.update_electric_dispersive_A["func"].substitute(subs),
knl_fields_updates.update_electric_dispersive_A["func"].substitute(
subs
),
"update_electric_dispersive_A",
preamble=self.knl_common,
options=config.sim_config.devices["compiler_opts"],
)
self.dispersive_update_b = self.elwise(
self.dispersive_update_b = self.elwiseknl(
self.ctx,
knl_fields_updates.update_electric_dispersive_B["args_opencl"].substitute(
knl_fields_updates.update_electric_dispersive_B[
"args_opencl"
].substitute(
{
"REAL": config.sim_config.dtypes["C_float_or_double"],
"COMPLEX": config.get_model_config().materials["dispersiveCdtype"],
"COMPLEX": config.get_model_config().materials[
"dispersiveCdtype"
],
}
),
knl_fields_updates.update_electric_dispersive_B["func"].substitute(subs),
knl_fields_updates.update_electric_dispersive_B["func"].substitute(
subs
),
"update_electric_dispersive_B",
preamble=self.knl_common,
options=config.sim_config.devices["compiler_opts"],
@@ -973,10 +1071,12 @@ class OpenCLUpdates:
def _set_pml_knls(self):
"""PMLS - prepares kernels and gets kernel functions."""
knl_pml_updates_electric = import_module(
"gprMax.cuda_opencl.knl_pml_updates_electric_" + self.grid.pmls["formulation"]
"gprMax.cuda_opencl.knl_pml_updates_electric_"
+ self.grid.pmls["formulation"]
)
knl_pml_updates_magnetic = import_module(
"gprMax.cuda_opencl.knl_pml_updates_magnetic_" + self.grid.pmls["formulation"]
"gprMax.cuda_opencl.knl_pml_updates_magnetic_"
+ self.grid.pmls["formulation"]
)
subs = {
@@ -999,18 +1099,22 @@ class OpenCLUpdates:
knl_electric_name = getattr(knl_pml_updates_electric, knl_name)
knl_magnetic_name = getattr(knl_pml_updates_magnetic, knl_name)
pml.update_electric_dev = self.elwise(
pml.update_electric_dev = self.elwiseknl(
self.ctx,
knl_electric_name["args_opencl"].substitute({"REAL": config.sim_config.dtypes["C_float_or_double"]}),
knl_electric_name["args_opencl"].substitute(
{"REAL": config.sim_config.dtypes["C_float_or_double"]}
),
knl_electric_name["func"].substitute(subs),
f"pml_updates_electric_{knl_name}",
preamble=self.knl_common,
options=config.sim_config.devices["compiler_opts"],
)
pml.update_magnetic_dev = self.elwise(
pml.update_magnetic_dev = self.elwiseknl(
self.ctx,
knl_magnetic_name["args_opencl"].substitute({"REAL": config.sim_config.dtypes["C_float_or_double"]}),
knl_magnetic_name["args_opencl"].substitute(
{"REAL": config.sim_config.dtypes["C_float_or_double"]}
),
knl_magnetic_name["func"].substitute(subs),
f"pml_updates_magnetic_{knl_name}",
preamble=self.knl_common,
@@ -1022,7 +1126,7 @@ class OpenCLUpdates:
gets kernel function.
"""
self.rxcoords_dev, self.rxs_dev = htod_rx_arrays(self.grid, self.queue)
self.store_outputs_dev = self.elwise(
self.store_outputs_dev = self.elwiseknl(
self.ctx,
knl_store_outputs.store_outputs["args_opencl"].substitute(
{"REAL": config.sim_config.dtypes["C_float_or_double"]}
@@ -1038,48 +1142,63 @@ class OpenCLUpdates:
gets kernel function.
"""
if self.grid.hertziandipoles:
self.srcinfo1_hertzian_dev, self.srcinfo2_hertzian_dev, self.srcwaves_hertzian_dev = htod_src_arrays(
self.grid.hertziandipoles, self.grid, self.queue
)
self.update_hertzian_dipole_dev = self.elwise(
(
self.srcinfo1_hertzian_dev,
self.srcinfo2_hertzian_dev,
self.srcwaves_hertzian_dev,
) = htod_src_arrays(self.grid.hertziandipoles, self.grid, self.queue)
self.update_hertzian_dipole_dev = self.elwiseknl(
self.ctx,
knl_source_updates.update_hertzian_dipole["args_opencl"].substitute(
{"REAL": config.sim_config.dtypes["C_float_or_double"]}
),
knl_source_updates.update_hertzian_dipole["func"].substitute(
{"CUDA_IDX": "", "REAL": config.sim_config.dtypes["C_float_or_double"]}
{
"CUDA_IDX": "",
"REAL": config.sim_config.dtypes["C_float_or_double"],
}
),
"update_hertzian_dipole",
preamble=self.knl_common,
options=config.sim_config.devices["compiler_opts"],
)
if self.grid.magneticdipoles:
self.srcinfo1_magnetic_dev, self.srcinfo2_magnetic_dev, self.srcwaves_magnetic_dev = htod_src_arrays(
self.grid.magneticdipoles, self.grid, self.queue
)
self.update_magnetic_dipole_dev = self.elwise(
(
self.srcinfo1_magnetic_dev,
self.srcinfo2_magnetic_dev,
self.srcwaves_magnetic_dev,
) = htod_src_arrays(self.grid.magneticdipoles, self.grid, self.queue)
self.update_magnetic_dipole_dev = self.elwiseknl(
self.ctx,
knl_source_updates.update_magnetic_dipole["args_opencl"].substitute(
{"REAL": config.sim_config.dtypes["C_float_or_double"]}
),
knl_source_updates.update_magnetic_dipole["func"].substitute(
{"CUDA_IDX": "", "REAL": config.sim_config.dtypes["C_float_or_double"]}
{
"CUDA_IDX": "",
"REAL": config.sim_config.dtypes["C_float_or_double"],
}
),
"update_magnetic_dipole",
preamble=self.knl_common,
options=config.sim_config.devices["compiler_opts"],
)
if self.grid.voltagesources:
self.srcinfo1_voltage_dev, self.srcinfo2_voltage_dev, self.srcwaves_voltage_dev = htod_src_arrays(
self.grid.voltagesources, self.grid, self.queue
)
self.update_voltage_source_dev = self.elwise(
(
self.srcinfo1_voltage_dev,
self.srcinfo2_voltage_dev,
self.srcwaves_voltage_dev,
) = htod_src_arrays(self.grid.voltagesources, self.grid, self.queue)
self.update_voltage_source_dev = self.elwiseknl(
self.ctx,
knl_source_updates.update_voltage_source["args_opencl"].substitute(
{"REAL": config.sim_config.dtypes["C_float_or_double"]}
),
knl_source_updates.update_voltage_source["func"].substitute(
{"CUDA_IDX": "", "REAL": config.sim_config.dtypes["C_float_or_double"]}
{
"CUDA_IDX": "",
"REAL": config.sim_config.dtypes["C_float_or_double"],
}
),
"update_voltage_source",
preamble=self.knl_common,
@@ -1098,13 +1217,18 @@ class OpenCLUpdates:
self.snapHy_dev,
self.snapHz_dev,
) = htod_snapshot_array(self.grid, self.queue)
self.store_snapshot_dev = self.elwise(
self.store_snapshot_dev = self.elwiseknl(
self.ctx,
knl_snapshots.store_snapshot["args_opencl"].substitute(
{"REAL": config.sim_config.dtypes["C_float_or_double"]}
),
knl_snapshots.store_snapshot["func"].substitute(
{"CUDA_IDX": "", "NX_SNAPS": Snapshot.nx_max, "NY_SNAPS": Snapshot.ny_max, "NZ_SNAPS": Snapshot.nz_max}
{
"CUDA_IDX": "",
"NX_SNAPS": Snapshot.nx_max,
"NY_SNAPS": Snapshot.ny_max,
"NZ_SNAPS": Snapshot.nz_max,
}
),
"store_snapshot",
preamble=self.knl_common,
@@ -1114,7 +1238,7 @@ class OpenCLUpdates:
def store_outputs(self):
"""Stores field component values for every receiver."""
if self.grid.rxs:
event = self.store_outputs_dev(
self.store_outputs_dev(
np.int32(len(self.grid.rxs)),
np.int32(self.grid.iteration),
self.rxcoords_dev,
@@ -1126,7 +1250,6 @@ class OpenCLUpdates:
self.grid.Hy_dev,
self.grid.Hz_dev,
)
event.wait()
def store_snapshots(self, iteration):
"""Stores any snapshots.
@@ -1138,7 +1261,7 @@ class OpenCLUpdates:
for i, snap in enumerate(self.grid.snapshots):
if snap.time == iteration + 1:
snapno = 0 if config.get_model_config().device["snapsgpu2cpu"] else i
event = self.store_snapshot_dev(
self.store_snapshot_dev(
np.int32(snapno),
np.int32(snap.xs),
np.int32(snap.xf),
@@ -1162,7 +1285,7 @@ class OpenCLUpdates:
self.snapHy_dev,
self.snapHz_dev,
)
event.wait()
if config.get_model_config().device["snapsgpu2cpu"]:
dtoh_snapshot_array(
self.snapEx_dev.get(),
@@ -1177,7 +1300,7 @@ class OpenCLUpdates:
def update_magnetic(self):
"""Updates magnetic field components."""
event = self.update_magnetic_dev(
self.update_magnetic_dev(
np.int32(self.grid.nx),
np.int32(self.grid.ny),
np.int32(self.grid.nz),
@@ -1189,7 +1312,6 @@ class OpenCLUpdates:
self.grid.Ey_dev,
self.grid.Ez_dev,
)
event.wait()
def update_magnetic_pml(self):
"""Updates magnetic field components with the PML correction."""
@@ -1199,7 +1321,7 @@ class OpenCLUpdates:
def update_magnetic_sources(self):
"""Updates magnetic field components from sources."""
if self.grid.magneticdipoles:
event = self.update_magnetic_dipole_dev(
self.update_magnetic_dipole_dev(
np.int32(len(self.grid.magneticdipoles)),
np.int32(self.grid.iteration),
config.sim_config.dtypes["float_or_double"](self.grid.dx),
@@ -1213,13 +1335,12 @@ class OpenCLUpdates:
self.grid.Hy_dev,
self.grid.Hz_dev,
)
event.wait()
def update_electric_a(self):
"""Updates electric field components."""
# All materials are non-dispersive so do standard update.
if config.get_model_config().materials["maxpoles"] == 0:
event = self.update_electric_dev(
self.update_electric_dev(
np.int32(self.grid.nx),
np.int32(self.grid.ny),
np.int32(self.grid.nz),
@@ -1235,15 +1356,11 @@ class OpenCLUpdates:
# If there are any dispersive materials do 1st part of dispersive update
# (it is split into two parts as it requires present and updated electric field values).
else:
event = self.dispersive_update_a(
self.dispersive_update_a(
np.int32(self.grid.nx),
np.int32(self.grid.ny),
np.int32(self.grid.nz),
np.int32(config.get_model_config().materials["maxpoles"]),
self.grid.updatecoeffsdispersive_dev,
self.grid.Tx_dev,
self.grid.Ty_dev,
self.grid.Tz_dev,
self.grid.ID_dev,
self.grid.Ex_dev,
self.grid.Ey_dev,
@@ -1251,10 +1368,12 @@ class OpenCLUpdates:
self.grid.Hx_dev,
self.grid.Hy_dev,
self.grid.Hz_dev,
self.grid.updatecoeffsdispersive_dev,
self.grid.Tx_dev,
self.grid.Ty_dev,
self.grid.Tz_dev,
)
event.wait()
def update_electric_pml(self):
"""Updates electric field components with the PML correction."""
for pml in self.grid.pmls["slabs"]:
@@ -1265,7 +1384,7 @@ class OpenCLUpdates:
update any Hertzian dipole sources last.
"""
if self.grid.voltagesources:
event = self.update_voltage_source_dev(
self.update_voltage_source_dev(
np.int32(len(self.grid.voltagesources)),
np.int32(self.grid.iteration),
config.sim_config.dtypes["float_or_double"](self.grid.dx),
@@ -1279,10 +1398,9 @@ class OpenCLUpdates:
self.grid.Ey_dev,
self.grid.Ez_dev,
)
event.wait()
if self.grid.hertziandipoles:
event = self.update_hertzian_dipole_dev(
self.update_hertzian_dipole_dev(
np.int32(len(self.grid.hertziandipoles)),
np.int32(self.grid.iteration),
config.sim_config.dtypes["float_or_double"](self.grid.dx),
@@ -1296,7 +1414,6 @@ class OpenCLUpdates:
self.grid.Ey_dev,
self.grid.Ez_dev,
)
event.wait()
self.grid.iteration += 1
@@ -1308,21 +1425,20 @@ class OpenCLUpdates:
source updates.
"""
if config.get_model_config().materials["maxpoles"] > 0:
event = self.dispersive_update_b(
self.dispersive_update_b(
np.int32(self.grid.nx),
np.int32(self.grid.ny),
np.int32(self.grid.nz),
np.int32(config.get_model_config().materials["maxpoles"]),
self.grid.updatecoeffsdispersive_dev,
self.grid.Tx_dev,
self.grid.Ty_dev,
self.grid.Tz_dev,
self.grid.ID_dev,
self.grid.Ex_dev,
self.grid.Ey_dev,
self.grid.Ez_dev,
self.grid.updatecoeffsdispersive_dev,
self.grid.Tx_dev,
self.grid.Ty_dev,
self.grid.Tz_dev,
)
event.wait()
def time_start(self):
"""Starts event timers used to calculate solving time for model."""
@@ -1338,9 +1454,8 @@ class OpenCLUpdates:
Returns:
Memory (RAM) used on compute device.
"""
# if iteration == self.grid.iterations - 1:
# return self.drv.mem_get_info()[1] - self.drv.mem_get_info()[0]
logger.debug("Look at memory estimate for pyopencl")
# No clear way to determine memory used from PyOpenCL unlike PyCUDA.
pass
def calculate_solve_time(self):
"""Calculates solving time for model."""

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -70,9 +70,9 @@ class UserInput:
# Incorrect index
i = p[v.index(err.args[0])]
if name:
s = f"\n'{cmd_str}' {err.args[0]} {name}-coordinate {i * dl:g} " + "is not within the model domain"
s = f"\n'{cmd_str}' {err.args[0]} {name}-coordinate {i * dl:g} is not within the model domain"
else:
s = f"\n'{cmd_str}' {err.args[0]}-coordinate {i * dl:g} is not " + "within the model domain"
s = f"\n'{cmd_str}' {err.args[0]}-coordinate {i * dl:g} is not within the model domain"
logger.exception(s)
raise
@@ -108,7 +108,9 @@ class MainGridUserInput(UserInput):
p = self.check_point(p, cmd_str, name)
if self.grid.within_pml(p):
logger.warning(f"'{cmd_str}' sources and receivers should not " + "normally be positioned within the PML.")
logger.warning(
f"'{cmd_str}' sources and receivers should not normally be positioned within the PML."
)
return p
@@ -117,7 +119,9 @@ class MainGridUserInput(UserInput):
p2 = self.check_point(p2, cmd_str, name="upper")
if np.greater(p1, p2).any():
logger.exception(f"'{cmd_str}' the lower coordinates should be less " + "than the upper coordinates.")
logger.exception(
f"'{cmd_str}' the lower coordinates should be less than the upper coordinates."
)
raise ValueError
return p1, p2
@@ -152,7 +156,9 @@ class SubgridUserInput(MainGridUserInput):
super().__init__(grid)
# Defines the region exposed to the user
self.inner_bound = np.array([grid.n_boundary_cells_x, grid.n_boundary_cells_y, grid.n_boundary_cells_z])
self.inner_bound = np.array(
[grid.n_boundary_cells_x, grid.n_boundary_cells_y, grid.n_boundary_cells_z]
)
self.outer_bound = np.subtract([grid.nx, grid.ny, grid.nz], self.inner_bound)
@@ -185,8 +191,13 @@ class SubgridUserInput(MainGridUserInput):
# Provide user within a warning if they have placed objects within
# the OS non-working region.
if np.less(p_t, self.inner_bound).any() or np.greater(p_t, self.outer_bound).any():
logger.warning(f"'{cmd_str}' this object traverses the Outer " + "Surface. This is an advanced feature.")
if (
np.less(p_t, self.inner_bound).any()
or np.greater(p_t, self.outer_bound).any()
):
logger.warning(
f"'{cmd_str}' this object traverses the Outer Surface. This is an advanced feature."
)
return p_t
def discretise_static_point(self, p):

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -48,7 +48,11 @@ def get_host_info():
# Manufacturer/model
try:
manufacturer = (
subprocess.check_output(["wmic", "csproduct", "get", "vendor"], shell=False, stderr=subprocess.STDOUT)
subprocess.check_output(
["wmic", "csproduct", "get", "vendor"],
shell=False,
stderr=subprocess.STDOUT,
)
.decode("utf-8")
.strip()
)
@@ -59,7 +63,9 @@ def get_host_info():
manufacturer = manufacturer[0]
model = (
subprocess.check_output(
["wmic", "computersystem", "get", "model"], shell=False, stderr=subprocess.STDOUT
["wmic", "computersystem", "get", "model"],
shell=False,
stderr=subprocess.STDOUT,
)
.decode("utf-8")
.strip()
@@ -76,7 +82,11 @@ def get_host_info():
# CPU information
try:
allcpuinfo = (
subprocess.check_output(["wmic", "cpu", "get", "Name"], shell=False, stderr=subprocess.STDOUT)
subprocess.check_output(
["wmic", "cpu", "get", "Name"],
shell=False,
stderr=subprocess.STDOUT,
)
.decode("utf-8")
.strip()
)
@@ -109,7 +119,9 @@ def get_host_info():
manufacturer = "Apple"
try:
model = (
subprocess.check_output(["sysctl", "-n", "hw.model"], shell=False, stderr=subprocess.STDOUT)
subprocess.check_output(
["sysctl", "-n", "hw.model"], shell=False, stderr=subprocess.STDOUT
)
.decode("utf-8")
.strip()
)
@@ -120,14 +132,20 @@ def get_host_info():
# CPU information
try:
sockets = (
subprocess.check_output(["sysctl", "-n", "hw.packages"], shell=False, stderr=subprocess.STDOUT)
subprocess.check_output(
["sysctl", "-n", "hw.packages"],
shell=False,
stderr=subprocess.STDOUT,
)
.decode("utf-8")
.strip()
)
sockets = int(sockets)
cpuID = (
subprocess.check_output(
["sysctl", "-n", "machdep.cpu.brand_string"], shell=False, stderr=subprocess.STDOUT
["sysctl", "-n", "machdep.cpu.brand_string"],
shell=False,
stderr=subprocess.STDOUT,
)
.decode("utf-8")
.strip()
@@ -150,13 +168,19 @@ def get_host_info():
# Manufacturer/model
try:
manufacturer = (
subprocess.check_output(["cat", "/sys/class/dmi/id/sys_vendor"], shell=False, stderr=subprocess.STDOUT)
subprocess.check_output(
["cat", "/sys/class/dmi/id/sys_vendor"],
shell=False,
stderr=subprocess.STDOUT,
)
.decode("utf-8")
.strip()
)
model = (
subprocess.check_output(
["cat", "/sys/class/dmi/id/product_name"], shell=False, stderr=subprocess.STDOUT
["cat", "/sys/class/dmi/id/product_name"],
shell=False,
stderr=subprocess.STDOUT,
)
.decode("utf-8")
.strip()
@@ -170,7 +194,12 @@ def get_host_info():
# Locale to ensure English
myenv = {**os.environ, "LANG": "en_US.utf8"}
cpuIDinfo = (
subprocess.check_output(["cat", "/proc/cpuinfo"], shell=False, stderr=subprocess.STDOUT, env=myenv)
subprocess.check_output(
["cat", "/proc/cpuinfo"],
shell=False,
stderr=subprocess.STDOUT,
env=myenv,
)
.decode("utf-8")
.strip()
)
@@ -179,7 +208,9 @@ def get_host_info():
cpuID = re.sub(".*model name.*:", "", line, 1).strip()
cpuID = " ".join(cpuID.split())
allcpuinfo = (
subprocess.check_output(["lscpu"], shell=False, stderr=subprocess.STDOUT, env=myenv)
subprocess.check_output(
["lscpu"], shell=False, stderr=subprocess.STDOUT, env=myenv
)
.decode("utf-8")
.strip()
)
@@ -232,7 +263,7 @@ def print_host_info(hostinfo):
"""
hyperthreadingstr = (
f", {config.sim_config.hostinfo['logicalcores']} " f"cores with Hyper-Threading"
f", {config.sim_config.hostinfo['logicalcores']} cores with Hyper-Threading"
if config.sim_config.hostinfo["hyperthreading"]
else ""
)
@@ -301,12 +332,11 @@ def mem_check_host(mem):
mem: int for memory required (bytes).
"""
if mem > config.sim_config.hostinfo["ram"]:
logger.exception(
f"Memory (RAM) required ~{humanize.naturalsize(mem)} exceeds "
f"{humanize.naturalsize(config.sim_config.hostinfo['ram'], True)} "
"detected!\n"
logger.warning(
f"Memory (RAM) required (~{humanize.naturalsize(mem)}) exceeds "
f"({humanize.naturalsize(config.sim_config.hostinfo['ram'], True)}) "
" physical memory detected!\n"
)
raise ValueError
def mem_check_device_snaps(total_mem, snaps_mem):
@@ -324,12 +354,11 @@ def mem_check_device_snaps(total_mem, snaps_mem):
device_mem = config.get_model_config().device["dev"].global_mem_size
if total_mem - snaps_mem > device_mem:
logger.exception(
f"Memory (RAM) required ~{humanize.naturalsize(total_mem)} exceeds "
f"{humanize.naturalsize(device_mem, True)} "
f"detected on specified {' '.join(config.get_model_config().device['dev'].name.split())} device!\n"
logger.warning(
f"Memory (RAM) required (~{humanize.naturalsize(total_mem)}) exceeds "
f"({humanize.naturalsize(device_mem, True)}) physical memory detected "
f"on specified {' '.join(config.get_model_config().device['dev'].name.split())} device!\n"
)
raise ValueError
# If the required memory without the snapshots will fit on the GPU then
# transfer and store snaphots on host
@@ -492,12 +521,12 @@ def detect_cuda_gpus():
def print_cuda_info(devs):
""""Prints info about detected CUDA-capable GPU(s).
"""Prints info about detected CUDA-capable GPU(s).
Args:
devs: dict of detected pycuda device object(s) where where device ID(s)
are keys.
""" ""
"""
import pycuda
@@ -547,12 +576,12 @@ def detect_opencl():
def print_opencl_info(devs):
""""Prints info about detected OpenCL-capable device(s).
"""Prints info about detected OpenCL-capable device(s).
Args:
devs: dict of detected pyopencl device object(s) where where device ID(s)
are keys.
""" ""
"""
import pyopencl as cl

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -68,7 +68,9 @@ class CustomFormatter(logging.Formatter):
return logging.Formatter.format(self, colored_record)
def logging_config(name="gprMax", level=logging.INFO, format_style="std", log_file=False):
def logging_config(
name="gprMax", level=logging.INFO, format_style="std", log_file=False
):
"""Setup and configure logging.
Args:
@@ -102,7 +104,9 @@ def logging_config(name="gprMax", level=logging.INFO, format_style="std", log_fi
# Config for logging to file if required
if log_file:
filename = name + "-log-" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S") + ".txt"
filename = (
name + "-log-" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S") + ".txt"
)
handler = logging.FileHandler(filename, mode="w")
formatter = logging.Formatter(format_full)
handler.setLevel(logging.DEBUG)

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -22,6 +22,7 @@ import logging
import re
import textwrap
from shutil import get_terminal_size
from time import perf_counter as timer_fn
import numpy as np
from colorama import Fore, Style, init
@@ -30,17 +31,6 @@ init()
logger = logging.getLogger(__name__)
try:
from time import thread_time as timer_fn
except ImportError:
from time import perf_counter as timer_fn
logger.debug(
'"thread_time" not currently available in macOS and bug'
' (https://bugs.python.org/issue36205) with "process_time", '
'so use "perf_counter".'
)
def get_terminal_width():
"""Gets/sets width of terminal being used.
@@ -56,6 +46,16 @@ def get_terminal_width():
return terminalwidth
def atoi(text):
"""Converts a string into an integer."""
return int(text) if text.isdigit() else text
def natural_keys(text):
"""Human sorting of a string."""
return [atoi(c) for c in re.split(r"(\d+)", text)]
def logo(version):
"""Prints gprMax logo, version, and licencing/copyright information.
@@ -66,9 +66,15 @@ def logo(version):
str: string containing logo, version, and licencing/copyright info.
"""
description = "\n=== Electromagnetic modelling software based on the " "Finite-Difference Time-Domain (FDTD) method"
description = (
"\n=== Electromagnetic modelling software based on the "
"Finite-Difference Time-Domain (FDTD) method"
)
current_year = datetime.datetime.now().year
copyright = f"Copyright (C) 2015-{current_year}: The University of " "Edinburgh, United Kingdom"
copyright = (
f"Copyright (C) 2015-{current_year}: The University of "
"Edinburgh, United Kingdom"
)
authors = "Authors: Craig Warren, Antonis Giannopoulos, and John Hartley"
licenseinfo1 = (
"gprMax is free software: you can redistribute it and/or "
@@ -91,7 +97,7 @@ def logo(version):
)
logo = (
""" www.gprmax.com __ __
r""" www.gprmax.com __ __
__ _ _ __ _ __| \/ | __ ___ __
/ _` | '_ \| '__| |\/| |/ _` \ \/ /
| (_| | |_) | | | | | | (_| |> <
@@ -104,15 +110,39 @@ def logo(version):
str = f"{description} {'=' * (get_terminal_width() - len(description) - 1)}\n\n"
str += f"{Fore.CYAN}{logo}"
str += Style.RESET_ALL + textwrap.fill(copyright, width=get_terminal_width() - 1, initial_indent=" ") + "\n"
str += textwrap.fill(authors, width=get_terminal_width() - 1, initial_indent=" ") + "\n\n"
str += (
textwrap.fill(licenseinfo1, width=get_terminal_width() - 1, initial_indent=" ", subsequent_indent=" ") + "\n"
Style.RESET_ALL
+ textwrap.fill(copyright, width=get_terminal_width() - 1, initial_indent=" ")
+ "\n"
)
str += (
textwrap.fill(licenseinfo2, width=get_terminal_width() - 1, initial_indent=" ", subsequent_indent=" ") + "\n"
textwrap.fill(authors, width=get_terminal_width() - 1, initial_indent=" ")
+ "\n\n"
)
str += (
textwrap.fill(
licenseinfo1,
width=get_terminal_width() - 1,
initial_indent=" ",
subsequent_indent=" ",
)
+ "\n"
)
str += (
textwrap.fill(
licenseinfo2,
width=get_terminal_width() - 1,
initial_indent=" ",
subsequent_indent=" ",
)
+ "\n"
)
str += textwrap.fill(
licenseinfo3,
width=get_terminal_width() - 1,
initial_indent=" ",
subsequent_indent=" ",
)
str += textwrap.fill(licenseinfo3, width=get_terminal_width() - 1, initial_indent=" ", subsequent_indent=" ")
return str
@@ -131,12 +161,16 @@ def round_value(value, decimalplaces=0):
# Rounds to nearest integer (half values are rounded downwards)
if decimalplaces == 0:
rounded = int(d.Decimal(value).quantize(d.Decimal("1"), rounding=d.ROUND_HALF_DOWN))
rounded = int(
d.Decimal(value).quantize(d.Decimal("1"), rounding=d.ROUND_HALF_DOWN)
)
# Rounds down to nearest float represented by number of decimal places
else:
precision = f"1.{'0' * decimalplaces}"
rounded = float(d.Decimal(value).quantize(d.Decimal(precision), rounding=d.ROUND_FLOOR))
rounded = float(
d.Decimal(value).quantize(d.Decimal(precision), rounding=d.ROUND_FLOOR)
)
return rounded
@@ -179,23 +213,3 @@ def fft_power(waveform, dt):
def timer():
"""Time in fractional seconds."""
return timer_fn()
def atoi(text):
"""Converts a string into an integer."""
return int(text) if text.isdigit() else text
def natural_keys(text):
"""Human sorting of a string."""
return [atoi(c) for c in re.split(r"(\d+)", text)]
def numeric_list_to_int_list(l):
"""List of int from a numerical list."""
return list(map(int, l))
def numeric_list_to_float_list(l):
"""List of float from a numerical list."""
return list(map(float, l))

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -66,7 +66,13 @@ class Waveform:
waveforms.
"""
if self.type in ["gaussian", "gaussiandot", "gaussiandotnorm", "gaussianprime", "gaussiandoubleprime"]:
if self.type in [
"gaussian",
"gaussiandot",
"gaussiandotnorm",
"gaussianprime",
"gaussiandoubleprime",
]:
self.chi = 1 / self.freq
self.zeta = 2 * np.pi**2 * self.freq**2
elif self.type in ["gaussiandotdot", "gaussiandotdotnorm", "ricker"]:
@@ -98,22 +104,41 @@ class Waveform:
elif self.type == "gaussiandotnorm":
delay = time - self.chi
normalise = np.sqrt(np.exp(1) / (2 * self.zeta))
ampvalue = -2 * self.zeta * delay * np.exp(-self.zeta * delay**2) * normalise
ampvalue = (
-2 * self.zeta * delay * np.exp(-self.zeta * delay**2) * normalise
)
elif self.type in ["gaussiandotdot", "gaussiandoubleprime"]:
delay = time - self.chi
ampvalue = 2 * self.zeta * (2 * self.zeta * delay**2 - 1) * np.exp(-self.zeta * delay**2)
ampvalue = (
2
* self.zeta
* (2 * self.zeta * delay**2 - 1)
* np.exp(-self.zeta * delay**2)
)
elif self.type == "gaussiandotdotnorm":
delay = time - self.chi
normalise = 1 / (2 * self.zeta)
ampvalue = 2 * self.zeta * (2 * self.zeta * delay**2 - 1) * np.exp(-self.zeta * delay**2) * normalise
ampvalue = (
2
* self.zeta
* (2 * self.zeta * delay**2 - 1)
* np.exp(-self.zeta * delay**2)
* normalise
)
elif self.type == "ricker":
delay = time - self.chi
normalise = 1 / (2 * self.zeta)
ampvalue = -(
(2 * self.zeta * (2 * self.zeta * delay**2 - 1) * np.exp(-self.zeta * delay**2)) * normalise
(
2
* self.zeta
* (2 * self.zeta * delay**2 - 1)
* np.exp(-self.zeta * delay**2)
)
* normalise
)
elif self.type == "sine":

10
pyproject.toml 普通文件
查看文件

@@ -0,0 +1,10 @@
[build-system]
requires = ['setuptools>=64', 'wheel', 'numpy>=1.19.0', 'Cython>=0.29.21',
'jinja2']
build-backend = "setuptools.build_meta"
[tool.black]
line-length = 120
[tool.isort]
profile = "black"

131
requirements.txt 普通文件
查看文件

@@ -0,0 +1,131 @@
aiofiles==23.2.1
aiosqlite==0.20.0
anyio==4.3.0
appnope==0.1.4
argon2-cffi==23.1.0
argon2-cffi-bindings==21.2.0
asttokens==2.4.1
attrs==23.2.0
Babel==2.14.0
backcall==0.2.0
beautifulsoup4==4.12.3
bleach==6.1.0
brotlipy==0.7.0
certifi==2024.2.2
cffi==1.16.0
charset-normalizer==3.3.2
click==8.1.7
colorama==0.4.6
comm==0.2.2
contourpy==1.2.0
cryptography==42.0.5
cycler==0.12.1
Cython==3.0.9
debugpy==1.8.1
decorator==5.1.1
defusedxml==0.7.1
entrypoints==0.4
evtk==2.0.0
executing==2.0.1
fastjsonschema==2.19.1
fonttools==4.50.0
gprMax==4.0.0b0
h5py==3.10.0
humanize==4.9.0
idna==3.6
ipykernel==6.29.3
ipython==8.22.2
ipython-genutils==0.2.0
ipywidgets==8.1.2
jedi==0.19.1
Jinja2==3.1.3
json5==0.9.24
jsonschema==4.21.1
jupyter==1.0.0
jupyter_client==8.6.1
jupyter-console==6.6.3
jupyter_core==5.7.2
jupyter-events==0.10.0
jupyter-server==2.13.0
jupyter_server_fileid==0.9.1
jupyter_server_ydoc==0.8.0
jupyter-ydoc==2.0.1
jupyterlab==4.1.5
jupyterlab-pygments==0.3.0
jupyterlab_server==2.25.4
jupyterlab-widgets==3.0.10
kiwisolver==1.4.5
lxml==5.1.0
MarkupSafe==2.1.5
matplotlib==3.8.3
matplotlib-inline==0.1.6
mistune==3.0.2
munkres==1.1.4
nbclassic==1.0.0
nbclient==0.10.0
nbconvert==7.16.2
nbformat==5.10.3
nest-asyncio==1.6.0
notebook==7.1.2
notebook_shim==0.2.4
numpy==1.26.4
numpy-stl==3.1.1
packaging==24.0
pandocfilters==1.5.1
parso==0.8.3
pexpect==4.9.0
pickleshare==0.7.5
Pillow==10.2.0
pip==24.0
platformdirs==4.2.0
ply==3.11
prometheus-client==0.20.0
prompt-toolkit==3.0.43
psutil==5.9.8
ptyprocess==0.7.0
pur==7.3.1
pure-eval==0.2.2
pycparser==2.21
Pygments==2.17.2
pyopencl==2024.1
pyOpenSSL==24.1.0
pyparsing==3.1.2
PyQt5-sip==12.13.0
pyrsistent==0.20.0
PySocks==1.7.1
python-dateutil==2.9.0.post0
python-json-logger==2.0.7
python-utils==3.8.2
pytools==2023.1.1
pytz==2024.1
PyYAML==6.0.1
pyzmq==25.1.2
qtconsole==5.5.1
QtPy==2.4.1
requests==2.31.0
rfc3339-validator==0.1.4
rfc3986-validator==0.1.1
scipy==1.12.0
Send2Trash==1.8.2
setuptools==69.2.0
sip==6.8.3
six==1.16.0
sniffio==1.3.1
soupsieve==2.5
stack-data==0.6.3
terminado==0.18.1
terminaltables==3.1.10
tinycss2==1.2.1
toml==0.10.2
tornado==6.4
tqdm==4.66.2
traitlets==5.14.2
typing_extensions==4.10.0
urllib3==2.2.1
wcwidth==0.2.13
webencodings==0.5.1
websocket-client==1.7.0
wheel==0.43.0
widgetsnbextension==4.0.10
y-py==0.6.2
ypy-websocket==0.12.4

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -18,6 +18,7 @@
import glob
import os
import platform
import re
import shutil
import subprocess
@@ -32,11 +33,15 @@ from setuptools import Extension, find_packages, setup
# Check Python version
MIN_PYTHON_VERSION = (3, 7)
if sys.version_info[:2] < MIN_PYTHON_VERSION:
sys.exit("\nExited: Requires Python {MIN_PYTHON_VERSION[0]}.{MIN_PYTHON_VERSION[1]} or newer!\n")
sys.exit(
"\nExited: Requires Python {MIN_PYTHON_VERSION[0]}.{MIN_PYTHON_VERSION[1]} or newer!\n"
)
# Importing gprMax _version__.py before building can cause issues.
with open("gprMax/_version.py", "r") as fd:
version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', fd.read(), re.MULTILINE)[1]
version = re.search(
r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', fd.read(), re.MULTILINE
)[1]
def build_dispersive_material_templates():
@@ -107,7 +112,9 @@ def build_dispersive_material_templates():
]
)
with open(os.path.join("gprMax", "cython", "fields_updates_dispersive.pyx"), "w") as f:
with open(
os.path.join("gprMax", "cython", "fields_updates_dispersive.pyx"), "w"
) as f:
f.write(r)
@@ -143,9 +150,9 @@ if "cleanall" in sys.argv:
except OSError:
print(f"Could not remove: {filebase}.c")
# Remove compiled Cython modules
libfile = glob.glob(os.path.join(os.getcwd(), os.path.splitext(file)[0]) + "*.pyd") + glob.glob(
os.path.join(os.getcwd(), os.path.splitext(file)[0]) + "*.so"
)
libfile = glob.glob(
os.path.join(os.getcwd(), os.path.splitext(file)[0]) + "*.pyd"
) + glob.glob(os.path.join(os.getcwd(), os.path.splitext(file)[0]) + "*.so")
if libfile:
libfile = libfile[0]
try:
@@ -181,41 +188,39 @@ else:
elif sys.platform == "darwin":
# Check for Intel or Apple M series CPU
cpuID = (
subprocess.check_output("sysctl -n machdep.cpu.brand_string", shell=True, stderr=subprocess.STDOUT)
subprocess.check_output(
"sysctl -n machdep.cpu.brand_string",
shell=True,
stderr=subprocess.STDOUT,
)
.decode("utf-8")
.strip()
)
cpuID = " ".join(cpuID.split())
if "Apple" in cpuID:
gccpath = glob.glob("/opt/homebrew/bin/gcc-[4-9]*")
gccpath += glob.glob("/opt/homebrew/bin/gcc-[10-11]*")
if gccpath:
# Use newest gcc found
os.environ["CC"] = gccpath[-1].split(os.sep)[-1]
rpath = "/opt/homebrew/opt/gcc/lib/gcc/" + gccpath[-1].split(os.sep)[-1][-1] + "/"
else:
raise (
"Cannot find gcc in /opt/homebrew/bin. gprMax requires gcc "
+ "to be installed - easily done through the Homebrew package "
+ "manager (http://brew.sh). Note: gcc with OpenMP support "
+ "is required."
gccbasepath = "/opt/homebrew/bin/"
else:
gccbasepath = "/usr/local/bin/"
gccpath = glob.glob(gccbasepath + "gcc-[0-9][0-9]")
if gccpath:
# Use newest gcc found
os.environ["CC"] = gccpath[-1].split(os.sep)[-1]
if "Apple" in cpuID:
rpath = (
"/opt/homebrew/opt/gcc/lib/gcc/"
+ gccpath[-1].split(os.sep)[-1][-1]
+ "/"
)
else:
gccpath = glob.glob("/usr/local/bin/gcc-[4-9]*")
gccpath += glob.glob("/usr/local/bin/gcc-[10-11]*")
if gccpath:
# Use newest gcc found
os.environ["CC"] = gccpath[-1].split(os.sep)[-1]
else:
raise (
"Cannot find gcc in /usr/local/bin. gprMax requires gcc "
+ "to be installed - easily done through the Homebrew package "
+ "manager (http://brew.sh). Note: gcc with OpenMP support "
+ "is required."
)
raise (
f"Cannot find gcc in {gccbasepath}. gprMax requires gcc "
+ "to be installed - easily done through the Homebrew package "
+ "manager (http://brew.sh). Note: gcc with OpenMP support "
+ "is required."
)
# Minimum supported macOS deployment target
MIN_MACOS_VERSION = "10.13"
# Set minimum supported macOS deployment target to installed macOS version
MIN_MACOS_VERSION = platform.mac_ver()[0]
try:
os.environ["MACOSX_DEPLOYMENT_TARGET"]
del os.environ["MACOSX_DEPLOYMENT_TARGET"]
@@ -223,7 +228,13 @@ else:
pass
os.environ["MIN_SUPPORTED_MACOSX_DEPLOYMENT_TARGET"] = MIN_MACOS_VERSION
# Sometimes worth testing with '-fstrict-aliasing', '-fno-common'
compile_args = ["-O3", "-w", "-fopenmp", "-march=native", f"-mmacosx-version-min={MIN_MACOS_VERSION}"]
compile_args = [
"-O3",
"-w",
"-fopenmp",
"-march=native",
f"-mmacosx-version-min={MIN_MACOS_VERSION}",
]
linker_args = ["-fopenmp", f"-mmacosx-version-min={MIN_MACOS_VERSION}"]
libraries = ["gomp"]
@@ -262,7 +273,7 @@ else:
)
# Parse long_description from README.rst file.
with open("README.rst", "r") as fd:
with open("README.rst", "r", encoding="utf-8") as fd:
long_description = fd.read()
setup(
@@ -270,7 +281,8 @@ else:
version=version,
author="Craig Warren, Antonis Giannopoulos, and John Hartley",
url="http://www.gprmax.com",
description="Electromagnetic Modelling Software based on the " + "Finite-Difference Time-Domain (FDTD) method",
description="Electromagnetic Modelling Software based on the "
+ "Finite-Difference Time-Domain (FDTD) method",
long_description=long_description,
long_description_content_type="text/x-rst",
license="GPLv3+",

查看文件

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
@@ -17,8 +17,9 @@
# along with gprMax. If not, see <http://www.gnu.org/licenses/>.
import numpy as np
from scipy.constants import c
from scipy.constants import epsilon_0 as e0
import gprMax.config as config
from gprMax.waveforms import Waveform
@@ -78,35 +79,35 @@ def hertzian_dipole_fs(iterations, dt, dxdydz, rx):
Ex_y = y
Ex_z = z - 0.5 * dz
Er_x = np.sqrt((Ex_x**2 + Ex_y**2 + Ex_z**2))
tau_Ex = Er_x / config.sim_config.em_consts["c"]
tau_Ex = Er_x / c
# Coordinates of Rx for Ey FDTD component
Ey_x = x
Ey_y = y + 0.5 * dy
Ey_z = z - 0.5 * dz
Er_y = np.sqrt((Ey_x**2 + Ey_y**2 + Ey_z**2))
tau_Ey = Er_y / config.sim_config.em_consts["c"]
tau_Ey = Er_y / c
# Coordinates of Rx for Ez FDTD component
Ez_x = x
Ez_y = y
Ez_z = z
Er_z = np.sqrt((Ez_x**2 + Ez_y**2 + Ez_z**2))
tau_Ez = Er_z / config.sim_config.em_consts["c"]
tau_Ez = Er_z / c
# Coordinates of Rx for Hx FDTD component
Hx_x = x
Hx_y = y + 0.5 * dy
Hx_z = z
Hr_x = np.sqrt((Hx_x**2 + Hx_y**2 + Hx_z**2))
tau_Hx = Hr_x / config.sim_config.em_consts["c"]
tau_Hx = Hr_x / c
# Coordinates of Rx for Hy FDTD component
Hy_x = x + 0.5 * dx
Hy_y = y
Hy_z = z
Hr_y = np.sqrt((Hy_x**2 + Hy_y**2 + Hy_z**2))
tau_Hy = Hr_y / config.sim_config.em_consts["c"]
tau_Hy = Hr_y / c
# Initialise fields
fields = np.zeros((iterations, 6))
@@ -134,7 +135,7 @@ def hertzian_dipole_fs(iterations, dt, dxdydz, rx):
fdot_Hy = wdot.calculate_value((timestep * dt) - tau_Hy, dt) * dl
# Ex
fields[timestep, 0] = ((Ex_x * Ex_z) / (4 * np.pi * config.sim_config.em_consts["e0"] * Er_x**5)) * (
fields[timestep, 0] = ((Ex_x * Ex_z) / (4 * np.pi * e0 * Er_x**5)) * (
3 * (fint_Ex + (tau_Ex * f_Ex)) + (tau_Ex**2 * fdot_Ex)
)
@@ -145,25 +146,29 @@ def hertzian_dipole_fs(iterations, dt, dxdydz, rx):
tmp = 0
fields[timestep, 1] = (
tmp
* ((Ey_x * Ey_z) / (4 * np.pi * config.sim_config.em_consts["e0"] * Er_y**5))
* ((Ey_x * Ey_z) / (4 * np.pi * e0 * Er_y**5))
* (3 * (fint_Ey + (tau_Ey * f_Ey)) + (tau_Ey**2 * fdot_Ey))
)
# Ez
fields[timestep, 2] = (1 / (4 * np.pi * config.sim_config.em_consts["e0"] * Er_z**5)) * (
fields[timestep, 2] = (1 / (4 * np.pi * e0 * Er_z**5)) * (
(2 * Ez_z**2 - (Ez_x**2 + Ez_y**2)) * (fint_Ez + (tau_Ez * f_Ez))
- (Ez_x**2 + Ez_y**2) * tau_Ez**2 * fdot_Ez
)
# Hx
fields[timestep, 3] = -(Hx_y / (4 * np.pi * Hr_x**3)) * (f_Hx + (tau_Hx * fdot_Hx))
fields[timestep, 3] = -(Hx_y / (4 * np.pi * Hr_x**3)) * (
f_Hx + (tau_Hx * fdot_Hx)
)
# Hy
try:
tmp = Hy_x / Hy_y
except ZeroDivisionError:
tmp = 0
fields[timestep, 4] = -tmp * (-(Hy_y / (4 * np.pi * Hr_y**3)) * (f_Hy + (tau_Hy * fdot_Hy)))
fields[timestep, 4] = -tmp * (
-(Hy_y / (4 * np.pi * Hr_y**3)) * (f_Hy + (tau_Hy * fdot_Hy))
)
# Hz
fields[timestep, 5] = 0

查看文件

@@ -1,9 +1,8 @@
"""A series of models with different domain sizes used for benchmarking.
The domain is free space with a simple source (Hertzian Dipole) and
receiver at the centre.
The domain is free space with a simple source (Hertzian Dipole) and
receiver at the centre.
"""
import itertools
from pathlib import Path
@@ -36,7 +35,9 @@ for d, threads in itertools.product(domains, ompthreads):
dxdydz = gprMax.Discretisation(p1=(dl, dl, dl))
time_window = gprMax.TimeWindow(time=3e-9)
wv = gprMax.Waveform(wave_type="gaussiandotnorm", amp=1, freq=900e6, id="MySource")
src = gprMax.HertzianDipole(p1=(x / 2, y / 2, z / 2), polarisation="x", waveform_id="MySource")
src = gprMax.HertzianDipole(
p1=(x / 2, y / 2, z / 2), polarisation="x", waveform_id="MySource"
)
omp = gprMax.OMPThreads(n=threads)

101
testing/diff_output_files.py 普通文件
查看文件

@@ -0,0 +1,101 @@
# Copyright (C) 2015-2025: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
#
# gprMax is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# gprMax is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with gprMax. If not, see <http://www.gnu.org/licenses/>.
import logging
from pathlib import Path
import h5py
import numpy as np
logger = logging.getLogger(__name__)
def diff_output_files(filename1, filename2):
"""Calculates differences between two output files.
Args:
filename1: string of filename (including path) of output file 1.
filename2: string of filename (including path) of output file 2.
Returns:
time: numpy array containing time.
datadiffs: numpy array containing power (dB) of differences.
"""
file1 = h5py.File(Path(filename1), "r")
file2 = h5py.File(Path(filename2), "r")
# Path to receivers in files
path = "rxs/rx1/"
# Get available field output component names
outputs1 = list(file1[path].keys())
outputs2 = list(file2[path].keys())
if outputs1 != outputs2:
logger.exception("Field output components are not the same in each file")
raise ValueError
# Check that type of float used to store fields matches
floattype1 = file1[path + outputs1[0]].dtype
floattype2 = file2[path + outputs2[0]].dtype
if floattype1 != floattype2:
logger.warning(
f"Type of floating point number in test model ({file1[path + outputs1[0]].dtype}) "
f"does not match type in reference solution ({file2[path + outputs2[0]].dtype})\n"
)
# Arrays for storing time
time1 = np.zeros((file1.attrs["Iterations"]), dtype=floattype1)
time1 = np.linspace(
0, (file1.attrs["Iterations"] - 1), num=file1.attrs["Iterations"]
)
time2 = np.zeros((file2.attrs["Iterations"]), dtype=floattype2)
time2 = np.linspace(
0, (file2.attrs["Iterations"] - 1), num=file2.attrs["Iterations"]
)
# Arrays for storing field data
data1 = np.zeros((file1.attrs["Iterations"], len(outputs1)), dtype=floattype1)
data2 = np.zeros((file2.attrs["Iterations"], len(outputs2)), dtype=floattype2)
for ID, name in enumerate(outputs1):
data1[:, ID] = file1[path + str(name)][:]
data2[:, ID] = file2[path + str(name)][:]
if np.any(np.isnan(data1[:, ID])) or np.any(np.isnan(data2[:, ID])):
logger.exception("Data contains NaNs")
raise ValueError
file1.close()
file2.close()
# Diffs
datadiffs = np.zeros(data1.shape, dtype=np.float64)
for i in range(len(outputs2)):
maxi = np.amax(np.abs(data1[:, i]))
datadiffs[:, i] = np.divide(
np.abs(data2[:, i] - data1[:, i]),
maxi,
out=np.zeros_like(data1[:, i]),
where=maxi != 0,
) # Replace any division by zero with zero
# Calculate power (ignore warning from taking a log of any zero values)
with np.errstate(divide="ignore"):
datadiffs[:, i] = 20 * np.log10(datadiffs[:, i])
# Replace any NaNs or Infs from zero division
datadiffs[:, i][np.invert(np.isfinite(datadiffs[:, i]))] = 0
return time1, datadiffs

查看文件

@@ -0,0 +1,15 @@
#title: Wire antenna - half-wavelength dipole in free-space
#domain: 0.050 0.050 0.200
#dx_dy_dz: 0.001 0.001 0.001
#time_window: 60e-9
#waveform: gaussian 1 1e9 mypulse
#transmission_line: z 0.025 0.025 0.100 73 mypulse
## 150mm length
#edge: 0.025 0.025 0.025 0.025 0.025 0.175 pec
## 1mm gap at centre of dipole
#edge: 0.025 0.025 0.100 0.025 0.025 0.101 free_space
geometry_view: 0.020 0.020 0.020 0.030 0.030 0.180 0.001 0.001 0.001 antenna_wire_dipole_fs f

查看文件

@@ -1,135 +0,0 @@
# Copyright (C) 2015-2023: The University of Edinburgh, United Kingdom
# Authors: Craig Warren, Antonis Giannopoulos, and John Hartley
#
# This file is part of gprMax.
#
# gprMax is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# gprMax is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with gprMax. If not, see <http://www.gnu.org/licenses/>.
import itertools
import logging
from operator import add
from pathlib import Path
import h5py
import matplotlib.pyplot as plt
import numpy as np
logger = logging.getLogger(__name__)
# Create/setup plot figure
# colors = ['#E60D30', '#5CB7C6', '#A21797', '#A3B347'] # Plot colours from http://tools.medialab.sciences-po.fr/iwanthue/index.php
# colorIDs = ["#62a85b", "#9967c7", "#b3943f", "#6095cd", "#cb5c42", "#c95889"]
colorIDs = ["#79c72e", "#5774ff", "#ff7c2c", "#4b4e80", "#d7004e", "#007545", "#ff83ec"]
# colorIDs = ["#ba0044", "#b2d334", "#470055", "#185300", "#ff96b1", "#3e2700", "#0162a9", "#fdb786"]
colors = itertools.cycle(colorIDs)
# for i in range(2):
# next(colors)
lines = itertools.cycle(("--", ":", "-.", "-"))
markers = ["o", "d", "^", "s", "*"]
parts = Path(__file__).parts
path = "rxs/rx1/"
basename = "pml_3D_pec_plate"
PMLIDs = ["CFS-PML", "HORIPML-1", "HORIPML-2", "MRIPML-1", "MRIPML-2"]
maxerrors = []
testmodels = ["pml_3D_pec_plate_" + s for s in PMLIDs]
fig, ax = plt.subplots(
subplot_kw=dict(xlabel="Iterations", ylabel="Error [dB]"), figsize=(20, 10), facecolor="w", edgecolor="w"
)
for x, model in enumerate(testmodels):
# Open output file and read iterations
fileref = h5py.File(Path(*parts[:-1], basename, basename + "_ref.h5"), "r")
filetest = h5py.File(Path(*parts[:-1], basename, basename + str(x + 1) + ".h5"), "r")
# Get available field output component names
outputsref = list(fileref[path].keys())
outputstest = list(filetest[path].keys())
if outputsref != outputstest:
logger.exception("Field output components do not match reference solution")
raise ValueError
# Check that type of float used to store fields matches
if filetest[path + outputstest[0]].dtype != fileref[path + outputsref[0]].dtype:
logger.warning(
f"Type of floating point number in test model ({filetest[path + outputstest[0]].dtype}) "
f"does not match type in reference solution ({fileref[path + outputsref[0]].dtype})\n"
)
floattyperef = fileref[path + outputsref[0]].dtype
floattypetest = filetest[path + outputstest[0]].dtype
# logger.info(f'Data type: {floattypetest}')
# Arrays for storing time
# timeref = np.zeros((fileref.attrs['Iterations']), dtype=floattyperef)
# timeref = np.linspace(0, (fileref.attrs['Iterations'] - 1) * fileref.attrs['dt'], num=fileref.attrs['Iterations']) / 1e-9
# timetest = np.zeros((filetest.attrs['Iterations']), dtype=floattypetest)
# timetest = np.linspace(0, (filetest.attrs['Iterations'] - 1) * filetest.attrs['dt'], num=filetest.attrs['Iterations']) / 1e-9
timeref = np.zeros((fileref.attrs["Iterations"]), dtype=floattyperef)
timeref = np.linspace(0, (fileref.attrs["Iterations"] - 1), num=fileref.attrs["Iterations"])
timetest = np.zeros((filetest.attrs["Iterations"]), dtype=floattypetest)
timetest = np.linspace(0, (filetest.attrs["Iterations"] - 1), num=filetest.attrs["Iterations"])
# Arrays for storing field data
dataref = np.zeros((fileref.attrs["Iterations"], len(outputsref)), dtype=floattyperef)
datatest = np.zeros((filetest.attrs["Iterations"], len(outputstest)), dtype=floattypetest)
for ID, name in enumerate(outputsref):
dataref[:, ID] = fileref[path + str(name)][:]
datatest[:, ID] = filetest[path + str(name)][:]
if np.any(np.isnan(datatest[:, ID])):
logger.exception("Test data contains NaNs")
raise ValueError
fileref.close()
filetest.close()
# Diffs
datadiffs = np.zeros(datatest.shape, dtype=np.float64)
for i in range(len(outputstest)):
maxi = np.amax(np.abs(dataref[:, i]))
datadiffs[:, i] = np.divide(
np.abs(datatest[:, i] - dataref[:, i]), maxi, out=np.zeros_like(dataref[:, i]), where=maxi != 0
) # Replace any division by zero with zero
# Calculate power (ignore warning from taking a log of any zero values)
with np.errstate(divide="ignore"):
datadiffs[:, i] = 20 * np.log10(datadiffs[:, i])
# Replace any NaNs or Infs from zero division
datadiffs[:, i][np.invert(np.isfinite(datadiffs[:, i]))] = 0
# Print maximum error value
start = 210
maxerrors.append(f": {np.amax(datadiffs[start::, 1]):.1f} [dB]")
logger.info(f"{model}: Max. error {maxerrors[x]}")
# Plot diffs (select column to choose field component, 0-Ex, 1-Ey etc..)
ax.plot(timeref[start::], datadiffs[start::, 1], color=next(colors), lw=2, ls=next(lines), label=model)
ax.set_xticks(np.arange(0, 2200, step=100))
ax.set_xlim([0, 2100])
ax.set_yticks(np.arange(-160, 0, step=20))
ax.set_ylim([-160, -20])
ax.set_axisbelow(True)
ax.grid(color=(0.75, 0.75, 0.75), linestyle="dashed")
mylegend = list(map(add, PMLIDs, maxerrors))
legend = ax.legend(mylegend, loc=1, fontsize=14)
frame = legend.get_frame()
frame.set_edgecolor("white")
frame.set_alpha(0)
plt.show()
# Save a PDF/PNG of the figure
# fig.savefig(basepath + '.pdf', dpi=None, format='pdf', bbox_inches='tight', pad_inches=0.1)
# fig.savefig(savename + '.png', dpi=150, format='png', bbox_inches='tight', pad_inches=0.1)

某些文件未显示,因为此 diff 中更改的文件太多 显示更多