Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Change log

## [v2.0.0-rc1](https://github.com/simvue-io/client/releases/tag/v2.0.0rc1) - 2025-03-06
* Add new example notebooks
* Update and refactor examples to work with v2.0
* Fix bug in offline artifacts using wrong file path
* Change names of sustainability metrics
* Fix `Self` being used in typing Generators so that Simvue works with Python 3.10 in Conda

## [v2.0.0-alpha3](https://github.com/simvue-io/client/releases/tag/v2.0.0a3) - 2025-03-04
* Updated codecarbon to work with new API
* Codecarbon now works with offline mode
Expand Down
6 changes: 3 additions & 3 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ keywords:
- alerting
- simulation
license: Apache-2.0
commit: 64ff8a5344232d44fc7da5b6ff601d3023497977
version: 2.0.0a3
date-released: '2025-03-04'
commit: effbd2e88fa12a181bf33721eae599d4245e1484
version: 2.0.0rc1
date-released: '2025-03-06'
references:
- title: mlco2/codecarbon
version: v2.8.2
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Collect metadata, metrics and artifacts from simulations, processing and AI/ML t

<div align="center">
<a href="https://github.com/simvue-io/client/blob/main/LICENSE" target="_blank"><img src="https://img.shields.io/github/license/simvue-io/client"/></a>
<img src="https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12-blue">
<img src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue">
<a href="https://pypi.org/project/simvue/" target="_blank"><img src="https://img.shields.io/pypi/v/simvue.svg"/></a>
<a href="https://pepy.tech/project/simvue"><img src="https://static.pepy.tech/badge/simvue"/></a>
<a href="https://github.com/astral-sh/ruff"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json"></a>
Expand Down
21 changes: 21 additions & 0 deletions examples/Bluemira/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Geometry optimisation using Bluemira

[Bluemira](https://github.com/Fusion-Power-Plant-Framework/bluemira) is an integrated inter-disciplinary design tool for future fusion reactors. It incorporates several modules, some of which rely on other codes, to carry out a range of typical conceptual fusion reactor design activities. This example uses Simvue to track the optimisation of the geometry of a Princeton-D shaped magnet, while maintaining a safe minimum distance to the plasma of 0.5m.


To run this example, you will need to install Bluemira. For details of installation of Bluemira please refer to https://bluemira.readthedocs.io/en/develop/installation.html

Once you have Bluemira installed and are running the `bluemita` conda environment (or similar), install Simvue with the plotting extras:
```
pip install simvue[plot]
```
Then move into the example's directory:
```
cd examples/Bluemira
```
Make a simvue.toml file - click Create New Run on the web UI, copy the contents listed, and paste into a config file.

Finally, run the example:
```
python geometry_optimisation.py
```
197 changes: 197 additions & 0 deletions examples/Bluemira/geometry_optimisation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# bluemira is an integrated inter-disciplinary design tool for future fusion
# reactors. It incorporates several modules, some of which rely on other
# codes, to carry out a range of typical conceptual fusion reactor design
# activities.
#
# Copyright (C) 2021 M. Coleman, J. Cook, F. Franza, I.A. Maione, S. McIntosh,
# J. Morris, D. Short
#
# bluemira is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# bluemira 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with bluemira; if not, see <https://www.gnu.org/licenses/>.
"""
Geometry Optimisation

Example taken from: bluemira/examples/optimisation/geometry_optimisation.ex.py

In this example we will go through how to set up a simple geometry
optimisation, including a geometric constraint.

The problem to solve is, minimise the length of our wall boundary,
in the xz-plane, whilst keeping it a minimum distance from our plasma.

We will greatly simplify this problem by working with a circular
plasma, we will use a PrincetonD for the wall shape,
and set the minimum distance to half a meter.
"""

import numpy as np
import os
import sys
from bluemira.display import plot_2d
from bluemira.display.plotter import PlotOptions
from bluemira.geometry.optimisation import optimise_geometry
from bluemira.geometry.parameterisations import GeometryParameterisation, PrincetonD
from bluemira.geometry.tools import distance_to, make_circle
from bluemira.geometry.wire import BluemiraWire

import simvue

def f_objective(geom: GeometryParameterisation) -> float:
"""Objective function to minimise a shape's length."""
return geom.create_shape().length


def distance_constraint(
geom: GeometryParameterisation, boundary: BluemiraWire, min_distance: float, run: simvue.Run
) -> float:
"""
A constraint to keep a minimum distance between two shapes.

The constraint must be in the form f(x) <= 0, i.e., constraint
is satisfied if f(x) <= 0.

Since what we want is 'min_distance <= distance(A, B)', we rewrite
this in the form 'min_distance - distance(A, B) <= 0', and return
the left-hand side from this function.
"""
shape = geom.create_shape()
# Log all variables as metrics after each iteration, giving human readable names:
run.log_metrics(
{
"inboard_limb_radius": float(geom.variables["x1"].value),
"outboard_limb_radius": float(geom.variables["x2"].value),
"vertical_offset": float(geom.variables["dz"].value),
"length_of_wall": float(shape.length),
"distance_to_plasma": float(distance_to(shape, boundary)[0])
}
)
return min_distance - distance_to(shape, boundary)[0]

# The original example prints stuff to the console to track progress
# Instead of changing these lines to log events (since we probably want both),
# We can make a class which intercepts stdout and also sends messages to Simvue
class StdoutToSimvue():
def __init__(self, run: simvue.Run):
self.run = run

def write(self, message: str):
# Log the message as an event (so long as it isnt a blank line)
if message.strip():
run.log_event(message)
# And print to console as normal
sys.__stdout__.write(message)

def flush(self):
sys.__stdout__.flush()

# Here we will start doing our optimisation. First create a Simvue run,
# using the Run class as a context manager:
with simvue.Run() as run:
# Initialise our run:
run.init(
name="bluemira_geometry_optimisation",
folder="/simvue_client_demos",
visibility="tenant" if os.environ.get("CI") else None,
tags=["bluemira", "simvue_client_examples"],
description="Minimise the length of a parameterised geometry using gradient-based optimisation algorithm.",
)

# Redirect stdout so that print statements also get logged as events:
stdout_sender = StdoutToSimvue(run)
sys.stdout = stdout_sender

# Next define the shape of our plasma, and the minimum distance we want between
# our wall boundary and our plasma:
min_distance = 0.5
plasma = make_circle(radius=2, center=(8, 0, 0.25), axis=(0, 1, 0))

# As with any optimisation, it's important to pick a reasonable initial
# parameterisation.
wall_boundary = PrincetonD({
"x1": {"value": 4, "upper_bound": 6},
"x2": {"value": 12, "lower_bound": 10},
})

print("Initial parameterisation:")
print(wall_boundary.variables)
print(f"Length of wall : {wall_boundary.create_shape().length}")
print(f"Distance to plasma: {distance_to(wall_boundary.create_shape(), plasma)[0]}")

# Create metadata for our original parameters:
_metadata = {
var: {
"initial": wall_boundary.variables[var].value,
"lower_bound": wall_boundary.variables[var].lower_bound,
"upper_bound": wall_boundary.variables[var].upper_bound
}
for var in ["x1", "x2", "dz"]
}
run.update_metadata({"bluemira_parameters": _metadata})

# Create and upload an image of the initial design to Simvue
_plot = plot_2d([wall_boundary.create_shape(), plasma])
_fig = _plot.get_figure()
run.save_object(_fig, category="input", name="initial_shape")

# Optimise our geometry using a gradient descent method
result = optimise_geometry(
wall_boundary,
algorithm="SLSQP",
f_objective=f_objective,
opt_conditions={"ftol_abs": 1e-6},
keep_history=True,
ineq_constraints=[
{
"f_constraint": lambda g: distance_constraint(g, plasma, min_distance, run),
"tolerance": np.array([1e-8]),
},
],
)

# Print final results after optimisation
print("Optimised parameterisation:")
print(result.geom.variables)

boundary = result.geom.create_shape()
print(f"Length of wall : {boundary.length}")
print(f"Distance to plasma: {distance_to(boundary, plasma)[0]}")

# Update metadata with final optimised values
_metadata = {
var: {
"final": result.geom.variables[var].value,
}
for var in ["x1", "x2", "dz"]
}
run.update_metadata({"bluemira_parameters": _metadata})

# Create and upload an image of the optimised design to Simvue
_plot = plot_2d([boundary, plasma])
_fig = _plot.get_figure()
run.save_object(_fig, category="output", name="final_shape")

# Use the history to create and upload an image of the design iterations
geom = PrincetonD()
ax = plot_2d(plasma, show=False)
for i, (x, _) in enumerate(result.history):
geom.variables.set_values_from_norm(x)
wire = geom.create_shape()
wire_options = {
"alpha": 0.5 + ((i + 1) / len(result.history)) / 2,
"color": "red",
"linewidth": 0.1,
}
ax = plot_2d(wire, options=PlotOptions(wire_options=wire_options), ax=ax, show=False)
_plot = plot_2d(boundary, ax=ax, show=True)
_fig = _plot.get_figure()
run.save_object(_fig, category="output", name="design_iterations")
56 changes: 0 additions & 56 deletions examples/FDS/activate_vents.fds

This file was deleted.

47 changes: 0 additions & 47 deletions examples/FDS/fds_unlim

This file was deleted.

Loading
Loading