You are not logged in.
It's been a while since I posted anything to this topic....
today I am exploring with ChatGPT4o how we might automate one of GW Johnson's many images he has created for his course on basic orbital mechanics. My reasoning is that if we automate the images (or one of them in this case) we might be able to reach readers who are not math experts, but who remember enough from education to be able to understand and relate to animation.
ChatGPT4o has provided a set of instructions that I will attempt to follow on one of my computers that has Python installed.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from PIL import Image# Load the image
image_path = 'your_image_file.png'
image = Image.open(image_path)# Convert the image to an array
image_array = np.array(image)# Set up the figure and axis
fig, ax = plt.subplots()
im = ax.imshow(image_array)# Function to update the animation
def update(frame):
# For now, we'll just move the image slightly to the right
ax.set_xlim(frame, frame + image_array.shape[1])
return im,# Create the animation
ani = animation.FuncAnimation(fig, update, frames=np.arange(0, 100), blit=True)# Show the animation
plt.show()
1) Install library
pip install matplotlib pillow
2) Create animation script
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
from PIL import Image# Load the image
image_path = 'your_image_file.png' # Replace with your actual image file name
image = Image.open(image_path)# Convert the image to an array
image_array = np.array(image)# Set up the figure and axis
fig, ax = plt.subplots()
im = ax.imshow(image_array)# Function to update the animation
def update(frame):
# For now, we'll just move the image slightly to the right
ax.set_xlim(frame, frame + image_array.shape[1])
return im,# Create the animation
ani = animation.FuncAnimation(fig, update, frames=np.arange(0, 100), blit=True)# Show the animation
plt.show()
4) Run the script
python animate_image.py
The image should shift to the right in small steps
(th)
Offline
Python provides modules to support a web site. I'm interested in this capability because I'd like to (at least try to) provide online animation of the course material GW Johnson has developed to cover Basic Orbital Mechanics.
My initial hope was to run the software on our Azure Test site, but I've run into a snag there... the Python software wants to run on port 5000, and so far the Azure environment has steadfastly refused to allow access to anything on that port. To be more accurate, the Azure environment allows access to port 5000 ** inside ** the environment at IP address 10.0.0.4, but it refuses to allow access to the port from the outside on 40.75.112.55.
After many hours of struggle, and after enlisting Microsoft Co-Pilot as well as ChatGPT4o (which may be the same thing for all I know) I finally threw in the towel and installed the code on my Linux workstation. The installation went smoothly, and the application ran immediately. What is ** more ** the website at port 5000 was accessible to my browser, and the run produced a flood of errors. While I would have preferred to see the program work as intended, at least we have ** something ** to look at, instead of the blank screen that showed up on Azure.
It occurs to me to wonder if all those errors might have occurred on Azure, but (perhaps?) it had no way to tell me about them? That remains to be seen.
In the mean time, there are a flood of errors to correct.
If anyone is interested, here is the Python code that produced the flood of errors:
# Test 01 Python Flask web site on port 5000
from flask import Flask, render_template
import numpy as np
import plotly.graph_objs as go
from astropy.constants import G
from astropy import units as uapp = Flask(__name__)
@app.route('/orbit')
def orbit():
def get_orbit(t, r0, v0, mu):
r = np.zeros((len(t), 3))
v = np.zeros((len(t), 3))
r[0] = r0
v[0] = v0
for i in range(1, len(t)):
dt = t{i} - t[i-1]
r_mag = np.linalg.norm(r[i-1])
a = -mu / r_mag**3 * r[i-1]
v{i} = v[i-1] + a * dt
r{i} = r[i-1] + v[i-1] * dt
return r, vr0 = np.array([7000.0, 0.0, 0.0])
v0 = np.array([0.0, 7.12, 0.0])
mu = G.to(u.km**3 / u.s**2).value * 5.972e24t = np.linspace(0, 5400, 1000)
r, v = get_orbit(t, r0, v0, mu)fig = go.Figure(data=[go.Scatter3d(x=r[:,0], y=r[:,1], z=r[:,2], mode='lines')])
fig.update_layout(scene=dict(
xaxis_title='X (km)',
yaxis_title='Y (km)',
zaxis_title='Z (km)'
))return fig.to_html(full_html=False)
@app.route('/')
def index():
return render_template('index.html', orbit_plot=orbit())if __name__ == '__main__':
app.run(debug=True)
I had to replace brackets with braces to persuade FluxBB to accept the code.
(th)
Offline
And here is the code that worked. The key problem had to do with how mu is calculated. The solution was to break the computation into two parts...
# Test 04 Python Flask web site on port 5000
# In Version 02 a units error is corrected
# In Version 03 we make an additional correction for mu
# In Version 04 we split calculation of mu into two steps
from flask import Flask, render_template
import numpy as np
import plotly.graph_objs as go
from astropy.constants import G
from astropy import units as uapp = Flask(__name__)
@app.route('/orbit')
def orbit():
def get_orbit(t, r0, v0, mu):
r = np.zeros((len(t), 3))
v = np.zeros((len(t), 3))
r[0] = r0
v[0] = v0
for i in range(1, len(t)):
dt = t{i} - t[i-1]
r_mag = np.linalg.norm(r[i-1])
a = -mu / r_mag**3 * r[i-1]
v{i} = v[i-1] + a * dt
r{i} = r[i-1] + v[i-1] * dt
return r, vr0 = np.array([7000.0, 0.0, 0.0])
v0 = np.array([0.0, 7.12, 0.0])
# mu = G.to(u.km**3 / u.s**2).value * 5.972e24 wrong units
# mu = (G * (u.km**3 / u.m**3)).to(u.km**3 / u.s**2).value * 5.972e24
# mu = (G * 5.972e24 * (u.km**3 / u.m**3)).to(u.km**3 / u.s**2).value
mu = G.value * 5.972e24 # G in m³ / (kg s²) and M in kg
mu = (mu * (u.m**3 / u.s**2)).to(u.km**3 / u.s**2).value # Convert to km³ / s²t = np.linspace(0, 5400, 1000)
r, v = get_orbit(t, r0, v0, mu)fig = go.Figure(data=[go.Scatter3d(x=r[:,0], y=r[:,1], z=r[:,2], mode='lines')])
fig.update_layout(scene=dict(
xaxis_title='X (km)',
yaxis_title='Y (km)',
zaxis_title='Z (km)'
))return fig.to_html(full_html=False)
@app.route('/')
def index():
return render_template('index.html', orbit_plot=orbit())if __name__ == '__main__':
app.run(debug=True)
As before, the bracket i bracket configuration was changed to {i}
I'd like to emphasize that practically anyone should be able to run this code. Python runs on Windows, Linux, Apple and (I'm told) on Android (with Linux installed as an app).
(th)
Offline
We don't seem to have anyone interested in working with Python at this point, but that could change at any time.
Here is a little Python program that ChatGPT4o designed to work on the little energy storage problem under consideration in another topic...
blocked by AISE ... our webmaster (kbd512) is working on an upgrade to our FluxBB system that will not have this problem.
(th)
Offline
The program ran without error.
I'm unable to verify the results, but here they are:
(Note that 1 kilowatt hour is 3,600,000 joules)
Mass of water input: 1000 kg
Air pressure in Compression tank: 1000000 pascal (1 bar)
Calculate:
Total energy stored: 198100 joules or about .05 kwh
Total height: 12.55 meters
Vacuum chamber height: 1.27 meters
Compression tank height: 1.27 meters
Doubling the mass of water doubles the energy stored.
Doubling the air pressure had a lesser effect: 298100 joules, up from 198100 joules
It occurs to me the mass of the water in the vertical pipe is not being taken into account. That mass has to be lifted in order to deliver the ton of water into the receiver.
It would be interesting to study the source code, but that will not be possible until the new version of FluxBB is up and running.
(th)
Offline
After thinking about the behavior of the little test program a bit more, I realized that this problem is one that is a good candidate for calculus, because the force required to move an increment of water from the vacuum tank to the compression tank is not constant. The amount of force required to deal with gravity is nearly constant, but not completely, because the top of the water column rises as water flows into the receiver tank. However, the main changes are at the ends. The force required to pull an increment of water out of the vacuum tank may increase as the volume of vacuum increases although I'm not sure about that. What I ** am ** sure about is that the force required to push an increment of water into the compression tank will increase. Thus, I expect that the theoretical performance of the system will improve as the code is refined. That said, I'm not expecting dramatic improvements. We are now looking at .05 kwh to move a ton of water, and that seems likely to increase slightly with the changes proposed.
(th)
Offline
This post will attempt to upload a revised version of the Energy Storage calculator program that was investigated last month.
This version eliminates the gravity storage component, and concentrates on gas compression storage.
The specific scenario of business interest is oil or gas wells that are in limbo, and thus potentially available for energy storage.
Because we have not yet completed upgrade of our software, the AISE error may block this attempt.
Unbelievable! The code was accepted. Anyone with a modern computer can install Python.
This program will run under Windows. Linux (where i work) and Apple.
I'm told it will run on Android devices (such as Chromebooks) if the Linux subsystem is installed.
This preliminary version should be considered an experiment to create the framework for the study.
However, that said, it does run without obvious errors, so the output may be correct. I certainly don't guarantee it.
# Compressed Gas Energy Storage Calculator - Version 3
# Focus: Evaluating energy storage potential in oil and gas wellsimport math
import tkinter as tk
from tkinter import messagebox# Function to calculate work of isothermal and adiabatic compression
def work_compression(p_initial, v_initial, v_final, gamma=1.4, isothermal=True):
if isothermal:
# Isothermal compression work: W = P_initial * V_initial * ln(V_initial / V_final)
return p_initial * v_initial * math.log(v_initial / v_final)
else:
# Adiabatic compression work: W = (P_initial * V_initial * [(V_final/V_initial)^(1-gamma) - 1]) / (gamma - 1)
return (p_initial * v_initial * ((v_initial / v_final) ** (gamma - 1) - 1)) / (1 - gamma)# Main function to perform the calculation
# Main function to perform the calculation
def calculate():
try:
# Retrieve input values from the GUI
P_initial = float(entry_P_initial.get()) * 1000 # Initial pressure (kPa to Pa)
P_final = float(entry_P_final.get()) * 1000 # Final pressure (kPa to Pa)
pipe_diameter = float(entry_pipe_diameter.get()) # Diameter of riser pipe (m)
pipe_length = float(entry_pipe_length.get()) # Height of riser pipe (m)
compression_type = compression_mode.get() # Compression type: Isothermal or Adiabatic# Derived values
pipe_area = math.pi * (pipe_diameter / 2) ** 2 # Cross-sectional area of the pipe (m²)
pipe_volume = pipe_area * pipe_length # Volume of the pipe (m³)# Choose the appropriate calculation method
isothermal = (compression_type == "Isothermal")
gamma = 1.4 # Specific heat ratio (default for air)# Calculate the compression work based on selected mode
compression_work = work_compression(P_initial, pipe_volume, pipe_volume / (P_final / P_initial), gamma, isothermal)# Convert compression work to kilojoules (kJ)
compression_work_kilojoules = compression_work / 1000# Display results in the output labels
output_compression_kj.config(text=f"{compression_work_kilojoules:.2f} kJ")except ValueError:
messagebox.showerror("Input Error", "Please enter valid numeric values.")# Set up the Tkinter GUI# Main function to perform the calculation
# revised calculate function for Version 03
def calculate():
try:
# Retrieve input values from the GUI
P_initial = float(entry_P_initial.get()) * 1000 # Initial pressure (kPa to Pa)
P_final = float(entry_P_final.get()) * 1000 # Final pressure (kPa to Pa)
pipe_diameter = float(entry_pipe_diameter.get()) # Diameter of riser pipe (m)
pipe_length = float(entry_pipe_length.get()) # Height of riser pipe (m)
compression_type = compression_mode.get() # Compression type: Isothermal or Adiabatic# Derived values
pipe_area = math.pi * (pipe_diameter / 2) ** 2 # Cross-sectional area of the pipe (m²)
pipe_volume = pipe_area * pipe_length # Volume of the pipe (m³)# Choose the appropriate calculation method
isothermal = (compression_type == "Isothermal")
gamma = 1.4 # Specific heat ratio (default for air)# Calculate the compression work based on selected mode
compression_work = work_compression(P_initial, pipe_volume, pipe_volume / (P_final / P_initial), gamma, isothermal)# Convert compression work to kilojoules (kJ)
compression_work_kilojoules = compression_work / 1000# Display results in the output labels
output_compression_kj.config(text=f"{compression_work_kilojoules:.2f} kJ")except ValueError:
messagebox.showerror("Input Error", "Please enter valid numeric values.")root = tk.Tk()
root.title("Compressed Gas Energy Storage Calculator - Version 3")# Labels and input fields
tk.Label(root, text="Initial Pressure (kPa):").grid(row=0, column=0, padx=10, pady=5)
entry_P_initial = tk.Entry(root)
entry_P_initial.grid(row=0, column=1, padx=10, pady=5)
entry_P_initial.insert(0, "101.325")tk.Label(root, text="Final Pressure (kPa):").grid(row=1, column=0, padx=10, pady=5)
entry_P_final = tk.Entry(root)
entry_P_final.grid(row=1, column=1, padx=10, pady=5)
entry_P_final.insert(0, "202.65")tk.Label(root, text="Riser Pipe Diameter (m):").grid(row=2, column=0, padx=10, pady=5)
entry_pipe_diameter = tk.Entry(root)
entry_pipe_diameter.grid(row=2, column=1, padx=10, pady=5)
entry_pipe_diameter.insert(0, "0.04")tk.Label(root, text="Riser Pipe Height (m):").grid(row=3, column=0, padx=10, pady=5)
entry_pipe_length = tk.Entry(root)
entry_pipe_length.grid(row=3, column=1, padx=10, pady=5)
entry_pipe_length.insert(0, "10")# Compression type selection
compression_mode = tk.StringVar(value="Isothermal")
tk.Label(root, text="Compression Type:").grid(row=4, column=0, padx=10, pady=5)
tk.Radiobutton(root, text="Isothermal", variable=compression_mode, value="Isothermal").grid(row=4, column=1, padx=10, pady=5)
tk.Radiobutton(root, text="Adiabatic", variable=compression_mode, value="Adiabatic").grid(row=4, column=2, padx=10, pady=5)# above is first half
# Output labels for results
tk.Label(root, text="Compression Work (kJ):").grid(row=5, column=0, padx=10, pady=5)
output_compression_kj = tk.Label(root, text="0.00 kJ")
output_compression_kj.grid(row=5, column=1, padx=10, pady=5)# Button to trigger calculation
calc_button = tk.Button(root, text="Calculate", command=calculate)
calc_button.grid(row=6, column=1, columnspan=2, pady=10)# Start the Tkinter event loop
root.mainloop()
(th)
Offline
Work on developing a browser accessible Python application has been ongoing for some time. There should be reports somewhere in the archive, but I can't remember where I put them. This topic is as good as any for a short progress report.
Today I worked with ChatGPT4o to update the previous version of pythontest05.py, which is available for viewing at:
http://40.75.112.55/flask or http://40.75.112.55/flask2
The new version is running on my development PC as pythontest06.py. It uses an html file: index06.html.
The new version has input fields for X, Y and Z, and a recalculate button.
I don't think the new version is ready for public evaluation.
A detail that may be of interest to someone in the readership is that the Linux "touch" command turned out to be useful for activating new code without restarting the Apache server. In the case of a Flask application such as this one, the file that we "touch" is flask.wsgi.
There may be something comparable in a php application, but I don't know that right now.
Today's concentration was getting the new updates to the Flask application to work.
(th)
Offline
ChatGPT4o and I are working on pythontest08.py. The program visible on the Azure web site is still the simple little starter program we used to get Flask running as an online service from Azure. Development is progressing on the local development PC.
This evening we hammered out a series of small steps to advance toward the goal of supporting GW Johnson's course on Basic Orbital mechanics.
For one thing, we now have a little blue Earth in the center of the 3D graph, and the orbit now closes properly. We've added a button to allow the student to try different starting positions for the satellite.
Enhancements to come include advancing from a simple circle to a true ellipse, which (after all) is the focus of the first lesson in GW's course.
(th)
Offline